blob: f803dc43e011e9dfd273fbdd10af88b66e5ad38f [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 Holdend05e5462006-05-26 18:26:21 +000038__version__ = '1.3'
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
Steve Holdenf8458772006-05-26 17:41:32 +0000129 def run(self, cruns):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000130
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
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000139 # first calibrate
Steve Holdenf8458772006-05-26 17:41:32 +0000140 t = clock()
141 calibrate()
142 offset = clock() - t
Steve Holden431a7632006-05-26 16:27:59 +0000143 if cruns:
Steve Holdenf8458772006-05-26 17:41:32 +0000144 for i in range(cruns-1):
Steve Holden431a7632006-05-26 16:27:59 +0000145 t = clock()
146 calibrate()
147 t = clock() - t
Steve Holdenf8458772006-05-26 17:41:32 +0000148 if t < offset:
149 offset = t
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000150 # now the real thing
Tim Petersf9cc5942006-04-21 16:34:54 +0000151 t = clock()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000152 test()
153 t = clock() - t
Steve Holdenf8458772006-05-26 17:41:32 +0000154 if t < 0.01:
155 sys.exit("Lower warp required: test times < 10 ms are unreliable")
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000156 self.last_timing = (t-offset,t,offset)
157 self.times.append(t-offset)
158
159 def calibrate(self):
160
Tim Petersf9cc5942006-04-21 16:34:54 +0000161 """ Calibrate the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000162
Tim Petersf9cc5942006-04-21 16:34:54 +0000163 This method should execute everything that is needed to
164 setup and run the test - except for the actual operations
165 that you intend to measure. pybench uses this method to
166 measure the test implementation overhead.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000167
168 """
169 return
170
171 def test(self):
172
Tim Petersf9cc5942006-04-21 16:34:54 +0000173 """ Run the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000174
Tim Petersf9cc5942006-04-21 16:34:54 +0000175 The test needs to run self.rounds executing
176 self.operations number of operations each.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000177
178 """
179 # do some tests
180 return
Tim Petersf9cc5942006-04-21 16:34:54 +0000181
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000182 def stat(self):
183
Steve Holden431a7632006-05-26 16:27:59 +0000184 """ Returns four values:
185 minimum round time
186 average time per round
187 average time per operation
188 average overhead time
Tim Petersf9cc5942006-04-21 16:34:54 +0000189
Steve Holden431a7632006-05-26 16:27:59 +0000190 XXX Should this take warp factors into account?
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000191 """
192 runs = len(self.times)
193 if runs == 0:
194 return 0,0
Steve Holden431a7632006-05-26 16:27:59 +0000195 mintime = min(self.times)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000196 totaltime = reduce(operator.add,self.times,0.0)
197 avg = totaltime / float(runs)
198 op_avg = totaltime / float(runs * self.rounds * self.operations)
199 if self.overhead:
200 totaloverhead = reduce(operator.add,self.overhead,0.0)
201 ov_avg = totaloverhead / float(runs)
202 else:
203 # use self.last_timing - not too accurate
204 ov_avg = self.last_timing[2]
Steve Holden431a7632006-05-26 16:27:59 +0000205 return mintime, avg, op_avg, ov_avg
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000206
207### Load Setup
208
209# This has to be done after the definition of the Test class, since
210# the Setup module will import subclasses using this class.
211
212import Setup
213
214### Benchmark base class
215
216class Benchmark:
217
218 name = '?' # Name of the benchmark
219 rounds = 1 # Number of rounds to run
220 warp = 1 # Warp factor
221 roundtime = 0 # Average round time
222 version = None # Benchmark version number (see __init__)
223 # as float x.yy
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000224
225 def __init__(self):
226
227 self.tests = {}
228 self.version = 0.31
229
Steve Holden431a7632006-05-26 16:27:59 +0000230 def load_tests(self, setupmod, warp=1, limitnames="", verbose=0):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000231
232 self.warp = warp
Steve Holden431a7632006-05-26 16:27:59 +0000233 if limitnames:
234 limitnames = re.compile(limitnames, re.I)
235 else:
236 limitnames = None
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000237 tests = self.tests
Steve Holden431a7632006-05-26 16:27:59 +0000238 if verbose:
239 print 'Searching for tests ...',
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000240 setupmod.__dict__.values()
241 for c in setupmod.__dict__.values():
Steve Holden431a7632006-05-26 16:27:59 +0000242 if not hasattr(c,'is_a_test'):
243 continue
244 name = c.__name__
245 if name == 'Test':
246 continue
247 if limitnames is not None and limitnames.search(name) is None:
248 continue
249 tests[name] = c(warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000250 l = tests.keys()
251 l.sort()
Steve Holden431a7632006-05-26 16:27:59 +0000252 if verbose:
253 print
254 for t in l:
255 print ' ', t
256 print len(l), "tests found"
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000257 print
258
Steve Holdenf8458772006-05-26 17:41:32 +0000259 def run(self, verbose, cruns):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000260
261 tests = self.tests.items()
262 tests.sort()
263 clock = time.clock
Steve Holden431a7632006-05-26 16:27:59 +0000264 print 'Running %i round(s) of the suite at warp factor %i:' % (self.rounds, self.warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000265 print
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000266 roundtime = clock()
267 for i in range(self.rounds):
Steve Holden431a7632006-05-26 16:27:59 +0000268 roundstarttime = clock()
269 if verbose:
270 print ' Round %-25i real abs overhead' % (i+1)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000271 for j in range(len(tests)):
Steve Holdenf8458772006-05-26 17:41:32 +0000272 name, t = tests[j]
Steve Holden431a7632006-05-26 16:27:59 +0000273 if verbose:
274 print '%30s:' % name,
Steve Holdenf8458772006-05-26 17:41:32 +0000275 t.run(cruns)
Steve Holden431a7632006-05-26 16:27:59 +0000276 if verbose:
277 print ' %.3fr %.3fa %.3fo' % t.last_timing
278 if verbose:
279 print ' ----------------------'
280 print ' Average round time: %.3f seconds' % \
281 ((clock() - roundtime)/(i+1))
282 print
283 else:
284 print '%d done in %.3f seconds' % (i+1, (clock() - roundstarttime))
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000285 self.roundtime = (clock() - roundtime) / self.rounds
286 print
Tim Petersf9cc5942006-04-21 16:34:54 +0000287
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000288 def print_stat(self, compare_to=None, hidenoise=0):
289
290 if not compare_to:
Steve Holden431a7632006-05-26 16:27:59 +0000291 print '%-30s min run avg run per oprn overhead' % 'Tests:'
292 print '-'*77
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000293 tests = self.tests.items()
294 tests.sort()
Steve Holden431a7632006-05-26 16:27:59 +0000295 totalmintime = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000296 for name,t in tests:
Steve Holden431a7632006-05-26 16:27:59 +0000297 mintime,avg,op_avg,ov_avg = t.stat()
298 totalmintime += mintime
299 print '%30s: %9.2f ms %9.2f ms %6.2f us %6.2f' % \
300 (name,mintime*1000.0,avg*1000.0,op_avg*1000000.0,ov_avg*1000.0)
301 print '-'*77
302 print '%30s: %9.2f ms' % \
303 ('Notional minimum round time', totalmintime * 1000.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000304
305 else:
Steve Holden431a7632006-05-26 16:27:59 +0000306 print 'Comparing with: %s (rounds=%i, warp=%i)' % \
307 (compare_to.name,compare_to.rounds,compare_to.warp)
308 print '%-30s min run cmp run avg run diff' % \
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000309 'Tests:'
Steve Holden431a7632006-05-26 16:27:59 +0000310 print '-'*77
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000311 tests = self.tests.items()
312 tests.sort()
313 compatible = 1
Steve Holden431a7632006-05-26 16:27:59 +0000314 totalmintime = other_totalmintime = 0
315 for name, t in tests:
316 mintime, avg, op_avg, ov_avg = t.stat()
317 totalmintime += mintime
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000318 try:
319 other = compare_to.tests[name]
320 except KeyError:
321 other = None
322 if other and other.version == t.version and \
323 other.operations == t.operations:
Steve Holden431a7632006-05-26 16:27:59 +0000324 mintime1, avg1, op_avg1, ov_avg1 = other.stat()
325 other_totalmintime += mintime1
326 diff = ((mintime*self.warp)/(mintime1*other.warp) - 1.0)*100.0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000327 if hidenoise and abs(qop_avg) < 10:
Steve Holden431a7632006-05-26 16:27:59 +0000328 diff = ''
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000329 else:
Steve Holden431a7632006-05-26 16:27:59 +0000330 diff = '%+7.2f%%' % diff
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000331 else:
Steve Holden431a7632006-05-26 16:27:59 +0000332 qavg, diff = 'n/a', 'n/a'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000333 compatible = 0
Steve Holden431a7632006-05-26 16:27:59 +0000334 print '%30s: %8.2f ms %8.2f ms %8.2f ms %8s' % \
335 (name,mintime*1000.0,mintime1*1000.0 * compare_to.warp/self.warp, avg*1000.0,diff)
336 print '-'*77
337 #
338 # Summarise test results
339 #
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000340 if compatible and compare_to.roundtime > 0 and \
341 compare_to.version == self.version:
Steve Holden431a7632006-05-26 16:27:59 +0000342 print '%30s: %8.2f ms %8.2f ms %+7.2f%%' % \
343 ('Notional minimum round time', totalmintime * 1000.0,
344 other_totalmintime * 1000.0 * compare_to.warp/self.warp,
345 ((totalmintime*self.warp)/
346 (other_totalmintime*compare_to.warp)-1.0)*100.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000347 else:
Steve Holden431a7632006-05-26 16:27:59 +0000348 print '%30s: %9.2f ms n/a' % \
349 ('Notional minimum round time', totalmintime * 1000.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000350 print
351
352def print_machine():
353
354 import platform
355 print 'Machine Details:'
356 print ' Platform ID: %s' % platform.platform()
357 print ' Executable: %s' % sys.executable
358 # There's a bug in Python 2.2b1+...
359 if sys.version[:6] == '2.2b1+':
360 return
361 print ' Python: %s' % platform.python_version()
362 print ' Compiler: %s' % platform.python_compiler()
363 buildno, builddate = platform.python_build()
364 print ' Build: %s (#%s)' % (builddate, buildno)
365
366class PyBenchCmdline(Application):
367
368 header = ("PYBENCH - a benchmark test suite for Python "
369 "interpreters/compilers.")
370
371 version = __version__
372
373 options = [ArgumentOption('-n','number of rounds',Setup.Number_of_rounds),
374 ArgumentOption('-f','save benchmark to file arg',''),
375 ArgumentOption('-c','compare benchmark with the one in file arg',''),
376 ArgumentOption('-s','show benchmark in file arg, then exit',''),
377 SwitchOption('-S','show statistics of benchmarks',0),
378 ArgumentOption('-w','set warp factor to arg',Setup.Warp_factor),
379 SwitchOption('-d','hide noise in compares', 0),
Steve Holden431a7632006-05-26 16:27:59 +0000380 SwitchOption('-v','verbose output (not recommended)', 0),
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000381 SwitchOption('--no-gc','disable garbage collection', 0),
Steve Holden431a7632006-05-26 16:27:59 +0000382 SwitchOption('--no-syscheck',
383 '"disable" sys check interval (set to sys.maxint)', 0),
384 ArgumentOption('-t', 'tests containing substring', ''),
Steve Holdenf8458772006-05-26 17:41:32 +0000385 ArgumentOption('-C', 'number of calibration runs (default 20)', 20)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000386 ]
387
388 about = """\
389The normal operation is to run the suite and display the
390results. Use -f to save them for later reuse or comparisms.
391
392Examples:
393
394python1.5 pybench.py -w 100 -f p15
395python1.4 pybench.py -w 100 -f p14
396python pybench.py -s p15 -c p14
397"""
398 copyright = __copyright__
399
400 def handle_S(self, value):
401
402 """ Display one line stats for each benchmark file given on the
403 command line.
404
405 """
406 for benchmark in self.files:
407 try:
408 f = open(benchmark, 'rb')
409 bench = pickle.load(f)
410 f.close()
411 except IOError:
412 print '* Error opening/reading file %s' % repr(benchmark)
413 else:
414 print '%s,%-.2f,ms' % (benchmark, bench.roundtime*1000.0)
415 return 0
416
417 def main(self):
418
419 rounds = self.values['-n']
420 reportfile = self.values['-f']
421 show_bench = self.values['-s']
422 compare_to = self.values['-c']
423 hidenoise = self.values['-d']
424 warp = self.values['-w']
425 nogc = self.values['--no-gc']
Steve Holden431a7632006-05-26 16:27:59 +0000426 limitnames = self.values['-t']
427 verbose = self.verbose
428 nosyscheck = self.values['--no-syscheck']
Steve Holdenf8458772006-05-26 17:41:32 +0000429 cruns = self.values['-C']
430 print "CRUNS:", cruns
431
Steve Holden431a7632006-05-26 16:27:59 +0000432 print 'PYBENCH',__version__
Tim Petersf9cc5942006-04-21 16:34:54 +0000433
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000434 # Switch off GC
435 if nogc:
436 try:
437 import gc
438 except ImportError:
439 nogc = 0
440 else:
441 if self.values['--no-gc']:
442 gc.disable()
Steve Holden431a7632006-05-26 16:27:59 +0000443 print 'NO GC'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000444
Steve Holden431a7632006-05-26 16:27:59 +0000445 # maximise sys check interval
446 if nosyscheck:
447 sys.setcheckinterval(sys.maxint)
448 print 'CHECKINTERVAL =', sys.maxint
449
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000450 print
451
452 if not compare_to:
453 print_machine()
454 print
455
456 if compare_to:
457 try:
458 f = open(compare_to,'rb')
459 bench = pickle.load(f)
460 bench.name = compare_to
461 f.close()
462 compare_to = bench
463 except IOError:
464 print '* Error opening/reading file',compare_to
Tim Petersf9cc5942006-04-21 16:34:54 +0000465 compare_to = None
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000466
467 if show_bench:
468 try:
469 f = open(show_bench,'rb')
470 bench = pickle.load(f)
471 bench.name = show_bench
472 f.close()
473 print 'Benchmark: %s (rounds=%i, warp=%i)' % \
474 (bench.name,bench.rounds,bench.warp)
475 print
476 bench.print_stat(compare_to, hidenoise)
477 except IOError:
478 print '* Error opening/reading file',show_bench
479 print
480 return
481
482 if reportfile:
483 if nogc:
484 print 'Benchmark: %s (rounds=%i, warp=%i, no GC)' % \
485 (reportfile,rounds,warp)
486 else:
487 print 'Benchmark: %s (rounds=%i, warp=%i)' % \
488 (reportfile,rounds,warp)
489 print
490
491 # Create benchmark object
492 bench = Benchmark()
493 bench.rounds = rounds
Steve Holden431a7632006-05-26 16:27:59 +0000494 bench.load_tests(Setup, warp, limitnames, verbose)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000495 try:
Steve Holdenf8458772006-05-26 17:41:32 +0000496 bench.run(verbose, cruns)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000497 except KeyboardInterrupt:
498 print
499 print '*** KeyboardInterrupt -- Aborting'
500 print
501 return
502 bench.print_stat(compare_to)
503 # ring bell
504 sys.stderr.write('\007')
505
506 if reportfile:
507 try:
508 f = open(reportfile,'wb')
509 bench.name = reportfile
510 pickle.dump(bench,f)
511 f.close()
512 except IOError:
513 print '* Error opening/writing reportfile'
514
515if __name__ == '__main__':
516 PyBenchCmdline()