blob: f190d77a923a7e7d083ba4810c56ccaf9a8af6bc [file] [log] [blame]
Marc-André Lemburgc311f642006-04-19 15:27:33 +00001#!/usr/local/bin/python -O
2
3""" A Python Benchmark Suite
4
5"""
6#
7# Note: Please keep this module compatible to Python 1.5.2.
8#
9# Tests may include features in later Python versions, but these
10# should then be embedded in try-except clauses in the configuration
11# module Setup.py.
12#
13
14# pybench Copyright
15__copyright__ = """\
16Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com)
17Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com)
18
19 All Rights Reserved.
20
21Permission to use, copy, modify, and distribute this software and its
22documentation for any purpose and without fee or royalty is hereby
23granted, provided that the above copyright notice appear in all copies
24and that both that copyright notice and this permission notice appear
25in supporting documentation or portions thereof, including
26modifications, that you make.
27
28THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO
29THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
30FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
31INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
32FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
33NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
34WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
35"""
36
37# Version number
Steve Holden431a7632006-05-26 16:27:59 +000038__version__ = '1.4'
Marc-André Lemburgc311f642006-04-19 15:27:33 +000039
40#
Tim Petersf9cc5942006-04-21 16:34:54 +000041# NOTE: Use xrange for all test loops unless you want to face
Marc-André Lemburgc311f642006-04-19 15:27:33 +000042# a 20MB process !
43#
44# All tests should have rounds set to values so that a run()
45# takes between 20-50 seconds. This is to get fairly good
46# clock() values. You can use option -w to speedup the tests
47# by a fixed integer factor (the "warp factor").
48#
49
50import sys,time,operator
51from CommandLine import *
52
53try:
54 import cPickle
55 pickle = cPickle
56except ImportError:
57 import pickle
58
59### Test baseclass
60
61class Test:
62
63 """ All test must have this class as baseclass. It provides
64 the necessary interface to the benchmark machinery.
65
66 The tests must set .rounds to a value high enough to let the
67 test run between 20-50 seconds. This is needed because
68 clock()-timing only gives rather inaccurate values (on Linux,
69 for example, it is accurate to a few hundreths of a
70 second). If you don't want to wait that long, use a warp
71 factor larger than 1.
72
73 It is also important to set the .operations variable to a
74 value representing the number of "virtual operations" done per
75 call of .run().
76
77 If you change a test in some way, don't forget to increase
78 it's version number.
79
80 """
81
82 ### Instance variables that each test should override
83
84 # Version number of the test as float (x.yy); this is important
85 # for comparisons of benchmark runs - tests with unequal version
86 # number will not get compared.
87 version = 1.0
Tim Petersf9cc5942006-04-21 16:34:54 +000088
Marc-André Lemburgc311f642006-04-19 15:27:33 +000089 # The number of abstract operations done in each round of the
90 # test. An operation is the basic unit of what you want to
91 # measure. The benchmark will output the amount of run-time per
92 # operation. Note that in order to raise the measured timings
93 # significantly above noise level, it is often required to repeat
94 # sets of operations more than once per test round. The measured
95 # overhead per test round should be less than 1 second.
96 operations = 1
97
98 # Number of rounds to execute per test run. This should be
99 # adjusted to a figure that results in a test run-time of between
100 # 20-50 seconds.
Steve Holden431a7632006-05-26 16:27:59 +0000101 rounds = 10000
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000102
103 ### Internal variables
104
105 # Mark this class as implementing a test
106 is_a_test = 1
107
108 # Misc. internal variables
109 last_timing = (0,0,0) # last timing (real,run,calibration)
110 warp = 1 # warp factor this test uses
111 cruns = 20 # number of calibration runs
112 overhead = None # list of calibration timings
113
114 def __init__(self,warp=1):
115
116 if warp > 1:
117 self.rounds = self.rounds / warp
Steve Holden431a7632006-05-26 16:27:59 +0000118 if self.rounds == 0:
119 self.rounds = 1
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000120 self.warp = warp
121 self.times = []
122 self.overhead = []
123 # We want these to be in the instance dict, so that pickle
124 # saves them
125 self.version = self.version
126 self.operations = self.operations
127 self.rounds = self.rounds
128
129 def run(self):
130
131 """ Run the test in two phases: first calibrate, then
132 do the actual test. Be careful to keep the calibration
133 timing low w/r to the test timing.
Tim Petersf9cc5942006-04-21 16:34:54 +0000134
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000135 """
136 test = self.test
137 calibrate = self.calibrate
138 clock = time.clock
139 cruns = self.cruns
140 # first calibrate
141 offset = 0.0
Steve Holden431a7632006-05-26 16:27:59 +0000142 if cruns:
143 for i in range(cruns):
144 t = clock()
145 calibrate()
146 t = clock() - t
147 offset = offset + t
148 offset = offset / cruns
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000149 # now the real thing
Tim Petersf9cc5942006-04-21 16:34:54 +0000150 t = clock()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000151 test()
152 t = clock() - t
153 self.last_timing = (t-offset,t,offset)
154 self.times.append(t-offset)
155
156 def calibrate(self):
157
Tim Petersf9cc5942006-04-21 16:34:54 +0000158 """ Calibrate the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000159
Tim Petersf9cc5942006-04-21 16:34:54 +0000160 This method should execute everything that is needed to
161 setup and run the test - except for the actual operations
162 that you intend to measure. pybench uses this method to
163 measure the test implementation overhead.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000164
165 """
166 return
167
168 def test(self):
169
Tim Petersf9cc5942006-04-21 16:34:54 +0000170 """ Run the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000171
Tim Petersf9cc5942006-04-21 16:34:54 +0000172 The test needs to run self.rounds executing
173 self.operations number of operations each.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000174
175 """
176 # do some tests
177 return
Tim Petersf9cc5942006-04-21 16:34:54 +0000178
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000179 def stat(self):
180
Steve Holden431a7632006-05-26 16:27:59 +0000181 """ Returns four values:
182 minimum round time
183 average time per round
184 average time per operation
185 average overhead time
Tim Petersf9cc5942006-04-21 16:34:54 +0000186
Steve Holden431a7632006-05-26 16:27:59 +0000187 XXX Should this take warp factors into account?
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000188 """
189 runs = len(self.times)
190 if runs == 0:
191 return 0,0
Steve Holden431a7632006-05-26 16:27:59 +0000192 mintime = min(self.times)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000193 totaltime = reduce(operator.add,self.times,0.0)
194 avg = totaltime / float(runs)
195 op_avg = totaltime / float(runs * self.rounds * self.operations)
196 if self.overhead:
197 totaloverhead = reduce(operator.add,self.overhead,0.0)
198 ov_avg = totaloverhead / float(runs)
199 else:
200 # use self.last_timing - not too accurate
201 ov_avg = self.last_timing[2]
Steve Holden431a7632006-05-26 16:27:59 +0000202 return mintime, avg, op_avg, ov_avg
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000203
204### Load Setup
205
206# This has to be done after the definition of the Test class, since
207# the Setup module will import subclasses using this class.
208
209import Setup
210
211### Benchmark base class
212
213class Benchmark:
214
215 name = '?' # Name of the benchmark
216 rounds = 1 # Number of rounds to run
217 warp = 1 # Warp factor
218 roundtime = 0 # Average round time
219 version = None # Benchmark version number (see __init__)
220 # as float x.yy
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000221
222 def __init__(self):
223
224 self.tests = {}
225 self.version = 0.31
226
Steve Holden431a7632006-05-26 16:27:59 +0000227 def load_tests(self, setupmod, warp=1, limitnames="", verbose=0):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000228
229 self.warp = warp
Steve Holden431a7632006-05-26 16:27:59 +0000230 if limitnames:
231 limitnames = re.compile(limitnames, re.I)
232 else:
233 limitnames = None
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000234 tests = self.tests
Steve Holden431a7632006-05-26 16:27:59 +0000235 if verbose:
236 print 'Searching for tests ...',
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000237 setupmod.__dict__.values()
238 for c in setupmod.__dict__.values():
Steve Holden431a7632006-05-26 16:27:59 +0000239 if not hasattr(c,'is_a_test'):
240 continue
241 name = c.__name__
242 if name == 'Test':
243 continue
244 if limitnames is not None and limitnames.search(name) is None:
245 continue
246 tests[name] = c(warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000247 l = tests.keys()
248 l.sort()
Steve Holden431a7632006-05-26 16:27:59 +0000249 if verbose:
250 print
251 for t in l:
252 print ' ', t
253 print len(l), "tests found"
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000254 print
255
Steve Holden431a7632006-05-26 16:27:59 +0000256 def run(self, verbose):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000257
258 tests = self.tests.items()
259 tests.sort()
260 clock = time.clock
Steve Holden431a7632006-05-26 16:27:59 +0000261 print 'Running %i round(s) of the suite at warp factor %i:' % (self.rounds, self.warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000262 print
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000263 roundtime = clock()
264 for i in range(self.rounds):
Steve Holden431a7632006-05-26 16:27:59 +0000265 roundstarttime = clock()
266 if verbose:
267 print ' Round %-25i real abs overhead' % (i+1)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000268 for j in range(len(tests)):
269 name,t = tests[j]
Steve Holden431a7632006-05-26 16:27:59 +0000270 if verbose:
271 print '%30s:' % name,
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000272 t.run()
Steve Holden431a7632006-05-26 16:27:59 +0000273 if verbose:
274 print ' %.3fr %.3fa %.3fo' % t.last_timing
275 if verbose:
276 print ' ----------------------'
277 print ' Average round time: %.3f seconds' % \
278 ((clock() - roundtime)/(i+1))
279 print
280 else:
281 print '%d done in %.3f seconds' % (i+1, (clock() - roundstarttime))
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000282 self.roundtime = (clock() - roundtime) / self.rounds
283 print
Tim Petersf9cc5942006-04-21 16:34:54 +0000284
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000285 def print_stat(self, compare_to=None, hidenoise=0):
286
287 if not compare_to:
Steve Holden431a7632006-05-26 16:27:59 +0000288 print '%-30s min run avg run per oprn overhead' % 'Tests:'
289 print '-'*77
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000290 tests = self.tests.items()
291 tests.sort()
Steve Holden431a7632006-05-26 16:27:59 +0000292 totalmintime = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000293 for name,t in tests:
Steve Holden431a7632006-05-26 16:27:59 +0000294 mintime,avg,op_avg,ov_avg = t.stat()
295 totalmintime += mintime
296 print '%30s: %9.2f ms %9.2f ms %6.2f us %6.2f' % \
297 (name,mintime*1000.0,avg*1000.0,op_avg*1000000.0,ov_avg*1000.0)
298 print '-'*77
299 print '%30s: %9.2f ms' % \
300 ('Notional minimum round time', totalmintime * 1000.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000301
302 else:
Steve Holden431a7632006-05-26 16:27:59 +0000303 print 'Comparing with: %s (rounds=%i, warp=%i)' % \
304 (compare_to.name,compare_to.rounds,compare_to.warp)
305 print '%-30s min run cmp run avg run diff' % \
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000306 'Tests:'
Steve Holden431a7632006-05-26 16:27:59 +0000307 print '-'*77
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000308 tests = self.tests.items()
309 tests.sort()
310 compatible = 1
Steve Holden431a7632006-05-26 16:27:59 +0000311 totalmintime = other_totalmintime = 0
312 for name, t in tests:
313 mintime, avg, op_avg, ov_avg = t.stat()
314 totalmintime += mintime
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000315 try:
316 other = compare_to.tests[name]
317 except KeyError:
318 other = None
319 if other and other.version == t.version and \
320 other.operations == t.operations:
Steve Holden431a7632006-05-26 16:27:59 +0000321 mintime1, avg1, op_avg1, ov_avg1 = other.stat()
322 other_totalmintime += mintime1
323 diff = ((mintime*self.warp)/(mintime1*other.warp) - 1.0)*100.0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000324 if hidenoise and abs(qop_avg) < 10:
Steve Holden431a7632006-05-26 16:27:59 +0000325 diff = ''
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000326 else:
Steve Holden431a7632006-05-26 16:27:59 +0000327 diff = '%+7.2f%%' % diff
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000328 else:
Steve Holden431a7632006-05-26 16:27:59 +0000329 qavg, diff = 'n/a', 'n/a'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000330 compatible = 0
Steve Holden431a7632006-05-26 16:27:59 +0000331 print '%30s: %8.2f ms %8.2f ms %8.2f ms %8s' % \
332 (name,mintime*1000.0,mintime1*1000.0 * compare_to.warp/self.warp, avg*1000.0,diff)
333 print '-'*77
334 #
335 # Summarise test results
336 #
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000337 if compatible and compare_to.roundtime > 0 and \
338 compare_to.version == self.version:
Steve Holden431a7632006-05-26 16:27:59 +0000339 print '%30s: %8.2f ms %8.2f ms %+7.2f%%' % \
340 ('Notional minimum round time', totalmintime * 1000.0,
341 other_totalmintime * 1000.0 * compare_to.warp/self.warp,
342 ((totalmintime*self.warp)/
343 (other_totalmintime*compare_to.warp)-1.0)*100.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000344 else:
Steve Holden431a7632006-05-26 16:27:59 +0000345 print '%30s: %9.2f ms n/a' % \
346 ('Notional minimum round time', totalmintime * 1000.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000347 print
348
349def print_machine():
350
351 import platform
352 print 'Machine Details:'
353 print ' Platform ID: %s' % platform.platform()
354 print ' Executable: %s' % sys.executable
355 # There's a bug in Python 2.2b1+...
356 if sys.version[:6] == '2.2b1+':
357 return
358 print ' Python: %s' % platform.python_version()
359 print ' Compiler: %s' % platform.python_compiler()
360 buildno, builddate = platform.python_build()
361 print ' Build: %s (#%s)' % (builddate, buildno)
362
363class PyBenchCmdline(Application):
364
365 header = ("PYBENCH - a benchmark test suite for Python "
366 "interpreters/compilers.")
367
368 version = __version__
369
370 options = [ArgumentOption('-n','number of rounds',Setup.Number_of_rounds),
371 ArgumentOption('-f','save benchmark to file arg',''),
372 ArgumentOption('-c','compare benchmark with the one in file arg',''),
373 ArgumentOption('-s','show benchmark in file arg, then exit',''),
374 SwitchOption('-S','show statistics of benchmarks',0),
375 ArgumentOption('-w','set warp factor to arg',Setup.Warp_factor),
376 SwitchOption('-d','hide noise in compares', 0),
Steve Holden431a7632006-05-26 16:27:59 +0000377 SwitchOption('-v','verbose output (not recommended)', 0),
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000378 SwitchOption('--no-gc','disable garbage collection', 0),
Steve Holden431a7632006-05-26 16:27:59 +0000379 SwitchOption('--no-syscheck',
380 '"disable" sys check interval (set to sys.maxint)', 0),
381 ArgumentOption('-t', 'tests containing substring', ''),
382 ArgumentOption('-C', 'number of calibration runs (default 0)', '')
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000383 ]
384
385 about = """\
386The normal operation is to run the suite and display the
387results. Use -f to save them for later reuse or comparisms.
388
389Examples:
390
391python1.5 pybench.py -w 100 -f p15
392python1.4 pybench.py -w 100 -f p14
393python pybench.py -s p15 -c p14
394"""
395 copyright = __copyright__
396
397 def handle_S(self, value):
398
399 """ Display one line stats for each benchmark file given on the
400 command line.
401
402 """
403 for benchmark in self.files:
404 try:
405 f = open(benchmark, 'rb')
406 bench = pickle.load(f)
407 f.close()
408 except IOError:
409 print '* Error opening/reading file %s' % repr(benchmark)
410 else:
411 print '%s,%-.2f,ms' % (benchmark, bench.roundtime*1000.0)
412 return 0
413
414 def main(self):
415
416 rounds = self.values['-n']
417 reportfile = self.values['-f']
418 show_bench = self.values['-s']
419 compare_to = self.values['-c']
420 hidenoise = self.values['-d']
421 warp = self.values['-w']
422 nogc = self.values['--no-gc']
Steve Holden431a7632006-05-26 16:27:59 +0000423 limitnames = self.values['-t']
424 verbose = self.verbose
425 nosyscheck = self.values['--no-syscheck']
426
427 print 'PYBENCH',__version__
Tim Petersf9cc5942006-04-21 16:34:54 +0000428
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000429 # Switch off GC
430 if nogc:
431 try:
432 import gc
433 except ImportError:
434 nogc = 0
435 else:
436 if self.values['--no-gc']:
437 gc.disable()
Steve Holden431a7632006-05-26 16:27:59 +0000438 print 'NO GC'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000439
Steve Holden431a7632006-05-26 16:27:59 +0000440 # maximise sys check interval
441 if nosyscheck:
442 sys.setcheckinterval(sys.maxint)
443 print 'CHECKINTERVAL =', sys.maxint
444
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000445 print
446
447 if not compare_to:
448 print_machine()
449 print
450
451 if compare_to:
452 try:
453 f = open(compare_to,'rb')
454 bench = pickle.load(f)
455 bench.name = compare_to
456 f.close()
457 compare_to = bench
458 except IOError:
459 print '* Error opening/reading file',compare_to
Tim Petersf9cc5942006-04-21 16:34:54 +0000460 compare_to = None
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000461
462 if show_bench:
463 try:
464 f = open(show_bench,'rb')
465 bench = pickle.load(f)
466 bench.name = show_bench
467 f.close()
468 print 'Benchmark: %s (rounds=%i, warp=%i)' % \
469 (bench.name,bench.rounds,bench.warp)
470 print
471 bench.print_stat(compare_to, hidenoise)
472 except IOError:
473 print '* Error opening/reading file',show_bench
474 print
475 return
476
477 if reportfile:
478 if nogc:
479 print 'Benchmark: %s (rounds=%i, warp=%i, no GC)' % \
480 (reportfile,rounds,warp)
481 else:
482 print 'Benchmark: %s (rounds=%i, warp=%i)' % \
483 (reportfile,rounds,warp)
484 print
485
486 # Create benchmark object
487 bench = Benchmark()
488 bench.rounds = rounds
Steve Holden431a7632006-05-26 16:27:59 +0000489 bench.load_tests(Setup, warp, limitnames, verbose)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000490 try:
Steve Holden431a7632006-05-26 16:27:59 +0000491 bench.run(verbose)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000492 except KeyboardInterrupt:
493 print
494 print '*** KeyboardInterrupt -- Aborting'
495 print
496 return
497 bench.print_stat(compare_to)
498 # ring bell
499 sys.stderr.write('\007')
500
501 if reportfile:
502 try:
503 f = open(reportfile,'wb')
504 bench.name = reportfile
505 pickle.dump(bench,f)
506 f.close()
507 except IOError:
508 print '* Error opening/writing reportfile'
509
510if __name__ == '__main__':
511 PyBenchCmdline()