summaryrefslogtreecommitdiff
path: root/benchmarks/CUDA/CP/driver/benchmark.py
blob: 43d78523ad89186c526daf40456f09b8472c7878 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
# (c) 2007 The Board of Trustees of the University of Illinois.

import sys
import os
from os import path
import re
from itertools import imap, repeat, chain

import globals
import process
from futures import Future

class Benchmark(object):
    """A benchmark.

    If the benchmark is malformed or otherwise invalid, only the 'name' and
    'invalid' fields will be set.  Otherwise all fields will be set.

    Fields:
      name        The name of the benchmark.  This is also the benchmark
                  directory name.
      invalid     None if the benchmark is valid; otherwise, an exception
                  describing why the benchmark is invalid.
      path        Full path of the benchmark directory.
      descr       A description of the benchmark.
      impls       A dictionary of benchmark source implementations.
      datas       A dictionary of data sets used to run the benchmark."""

    def __init__(self, name, path = None, impls = [], datasets = [],
                 description=None, invalid=None):
        self.name = name
        self.invalid = invalid

        if invalid is None:
            self.path = path
            self.impls = dict(imap(lambda i: (i.name, i), impls))
            self.datas = dict(imap(lambda i: (i.name, i), datasets))
            self.descr = description

    def createFromName(name):
        """Scan the benchmark directory for the benchmark named 'name'
        and create a benchmark object for it."""
        bmkpath = path.join(globals.root, 'benchmarks', name)
        descr = process.read_description_file(bmkpath)

        try:
            # Scan implementations of the benchmark
            impls = [BenchImpl.createFromName(name, impl)
                     for impl in process.scan_for_benchmark_versions(bmkpath)]
            
            # Scan data sets of the benchmark
            datas = [BenchDataset.createFromName(name, data)
                     for data in process.scan_for_benchmark_datasets(bmkpath)]

            # If no exception occurred, the benchmark is valid
            return Benchmark(name, bmkpath, impls, datas, descr)
        except Exception, e:
            return Benchmark(name, invalid=e)
        
    createFromName = staticmethod(createFromName)

    def describe(self):
        """Return a string describing this benchmark."""

        if self.invalid:
            return "Error in benchmark:\n" + str(self.invalid)

        if self.descr  is None:
            header = "Benchmark '" + self.name + "'"
        else:
            header = self.descr

        impls = " ".join([impl.name for impl in self.impls.itervalues()])
        datas = " ".join([data.name for data in self.datas.itervalues()])

        return header + "\nVersions: " + impls + "\nData sets: " + datas

    def instance_check(x):
        if not isinstance(x, Benchmark):
            raise TypeError, "argument must be an instance of Benchmark"

    instance_check = staticmethod(instance_check)

class BenchImpl(object):
    """An implementation of a benchmark."""

    def __init__(self, name, description=None):
        if not isinstance(name, str):
            raise TypeError, "name must be a string"

        self.name = name
        self.descr = description

    def createFromName(name, impl):
        """Scan the directory containing a benchmark implementation
        and create a BenchImpl object from it."""

        # Path to the implementation
        impl_path = path.join(globals.root, 'benchmarks', name, 'src', impl)

        # Get the description from a file, if provided
        descr = process.read_description_file(impl_path)

        return BenchImpl(impl, descr)

    createFromName = staticmethod(createFromName)

    def makefile(self, benchmark, target=None, action=None):
        """Run this implementation's makefile."""
        
        Benchmark.instance_check(benchmark)

        def perform():
            srcdir = path.join('src', self.name)
            builddir = path.join('build', self.name)

            env={'SRCDIR':srcdir,
                 'BUILDDIR':builddir,
                 'BIN':path.join(builddir,benchmark.name),
                 'PARBOIL_ROOT':globals.root}

            # Run the makefile to build the benchmark
            return process.makefile(target=target,
				    action=action,
                                    filepath=path.join(srcdir, "Makefile"),
                                    env=env)

        # Go to the benchmark directory before building
        return process.with_path(benchmark.path, perform)

    def build(self, benchmark):
        """Build an executable of this benchmark implementation."""
        return self.makefile(benchmark)

    def isBuilt(self, benchmark):
        """Determine whether the executable is up to date."""
        return self.makefile(benchmark, action='q')

    def clean(self, benchmark):
        """Remove build files for this benchmark implementation."""
        return self.makefile(benchmark, 'clean')

    def run(self, benchmark, dataset, do_output=True, extra_opts=[]):
        """Run this benchmark implementation.

        Return True if the benchmark terminated normally or False
        if there was an error."""

        # Ensure that the benchmark has been built
        if not self.isBuilt(benchmark):
            rc = self.build(benchmark)

            # Stop if 'make' failed
            if not rc: return False

        def perform():
            # Run the program
            exename = path.join('build', self.name, benchmark.name)
            args = [exename] + extra_opts + dataset.getCommandLineArguments(do_output)
            rc = process.spawnwaitv(exename, args)

            # Program exited with error?
            if rc != 0: return False
            return True

        return process.with_path(benchmark.path, perform)

    def check(self, benchmark, dataset):
        """Check the output from the last run of this benchmark
        implementation.

        Return True if the output checks successfully or False
        otherwise."""

        def perform():
            output_file = dataset.getTemporaryOutputPath()
            reference_file = dataset.getReferenceOutputPath()

            compare = os.path.join('tools', 'compare-output')
            rc = process.spawnwaitl(compare,
                                    compare, reference_file, output_file)

            # Program exited with error, or mismatch in output?
            if rc != 0: return False
            return True

        return process.with_path(benchmark.path, perform)

    def __str__(self):
        return "<BenchImpl '" + self.name + "'>"

class BenchDataset(object):
    """Data sets for running a benchmark."""

    def __init__(self, name, in_files=[], out_files=[], parameters=[],
                 description=None):
        if not isinstance(name, str):
            raise TypeError, "name must be a string"

        self.name = name
        self.inFiles = in_files
        self.outFiles = out_files
        self.parameters = parameters
        self.descr = description

    def createFromName(name, dset):
        """Scan the directory containing a dataset
        and create a BenchDataset object from it."""

        # Identify the paths where files may be found
        benchmark_path = path.join(globals.root, 'benchmarks', name)

        if path.exists(path.join(benchmark_path, 'input')):
            input_path = path.join(benchmark_path, 'input', dset)
        else:
            input_path = None
    
        output_path = path.join(benchmark_path, 'output', dset)

        # Look for input files
        
        def check_default_input_files():
            # This function is called to see if the input file set
            # guessed by scanning the input directory can be used
            if invalid_default_input_files:
                raise ValueError, "Cannot infer command line when there are multiple input files in a data set\n(Fix by adding an input DESCRIPTION file)"
                
        if input_path:
            input_descr = process.read_description_file(input_path)
            input_files = list(process.scan_for_files(input_path,
                                                      boring=['DESCRIPTION','.svn']))

            # If more than one input file was found, cannot use the default
            # input file list produced by scanning the directory
            invalid_default_input_files = len(input_files) > 1
        else:
            # If there's no input directory, assume the benchmark
            # takes no input
            input_descr = None
            input_files = []
            invalid_default_input_files = False

        # Read the text of the input description file
        if input_descr is not None:
            (parameters, input_files1, input_descr) = \
                unpack_dataset_description(input_descr, input_files=None)

            if input_files1 is None:
                # No override vaule given; use the default
                check_default_input_files()
            else:
                input_files = input_files1
        else:
            check_default_input_files()
            parameters = []

        # Look for output files
        output_descr = process.read_description_file(output_path)
        output_files = list(process.scan_for_files(output_path,
                                                   boring=['DESCRIPTION','.svn']))
        if len(output_files) > 1:
            raise ValueError, "Multiple output files not supported"

        # Concatenate input and output descriptions
        if input_descr and output_descr:
            descr = input_descr + "\n\n" + output_descr
        else:
            descr = input_descr or output_descr

        return BenchDataset(dset, input_files, output_files, parameters, descr)

    createFromName = staticmethod(createFromName)

    def getTemporaryOutputPath(self):
        """Get the name of a file used to hold the output of a benchmark run.
        This function should always return the same name if its parameters
        are the same.  The output path is not the path where the reference
        output is stored."""

        return path.join('run', self.name, self.outFiles[0])

    def getReferenceOutputPath(self):
        """Get the name of the reference file, to which the output of a
        benchmark run should be compared."""

        return path.join('output', self.name, self.outFiles[0])

    def getCommandLineArguments(self, do_output=True):
        """Get the command line arguments that should be passed to the
        executable to run this data set.  If 'output' is True, then
        the executable will be passed flags to save its output to a file.

        Directories to hold ouptut files are created if they do not exist."""
        args = []

        # Add arguments to pass input files to the benchmark
        if self.inFiles:
            in_files = ",".join([path.join('input', self.name, x)
                                 for x in self.inFiles])
            args.append("-i")
            args.append(in_files)

        # Add arguments to store the output somewhere, if output is
        # desired
        if do_output and self.outFiles:
            if len(self.outFiles) != 1:
                raise ValueError, "only one output file is supported"

            out_path = self.getTemporaryOutputPath()
            args.append("-o")
            args.append(out_path)

            # Ensure that a directory exists for the output
            process.touch_directory(path.dirname(out_path))

        args += self.parameters
        return args

    def __str__(self):
        return "<BenchData '" + self.name + "'>"

def unpack_dataset_description(descr, parameters=[], input_files=[]):
    """Read information from the raw contents of a data set description
    file.  Optional 'parameters' and 'input_files' arguments may be
    given, which will be retained unless overridden by the description
    file."""
    leftover = []
    split_at_colon = re.compile(r"^\s*([a-zA-Z]+)\s*:(.*)$")

    # Initialize these to default empty strings
    parameter_text = None
    input_file_text = None
    
    # Scan the description line by line
    for line in descr.split('\n'):
        m = split_at_colon.match(line)
        if m is None: continue

        # This line appears to declare something that should be
        # interpreted
        keyword = m.group(1)
        if keyword == "Parameters":
            parameter_text = m.group(2)
        elif keyword == "Inputs":
            input_file_text = m.group(2)
        # else, ignore the line

    # Split the strings into (possibly) multiple arguments, discarding
    # whitespace
    if parameter_text is not None: parameters = parameter_text.split()
    if input_file_text is not None: input_files = input_file_text.split()
    return (parameters, input_files, descr)

def find_benchmarks():
    """Find benchmarks in the repository.  The benchmarks are
    identified, but their contents are not scanned immediately.  A
    dictionary is returned mapping benchmark names to futures
    containing the benchmarks."""

    if not globals.root:
        raise ValueError, "root directory has not been set"

    # Scan all benchmarks in the 'benchmarks' directory and
    # lazily create benchmark objects.
    db = {}
    try:
        for bmkname in process.scan_for_benchmarks(globals.root):
            bmk = Future(lambda bmkname=bmkname: Benchmark.createFromName(bmkname))
            db[bmkname] = bmk
    except OSError, e:
        sys.stdout.write("Benchmark directory not found!\n\n")
        return {}

    return db