blob: e0110d09d0d18c1d7467ef8a47735b63f9fed04d [file] [log] [blame]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
38__version__ = '1.3'
39
40#
Thomas Wouters477c8d52006-05-27 19:21:47 +000041# NOTE: Use xrange for all test loops unless you want to face
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters477c8d52006-05-27 19:21:47 +000088
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000101 rounds = 10000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters477c8d52006-05-27 19:21:47 +0000118 if self.rounds == 0:
119 self.rounds = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters477c8d52006-05-27 19:21:47 +0000129 def run(self, cruns):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000134
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000135 """
136 test = self.test
137 calibrate = self.calibrate
138 clock = time.clock
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000139 # first calibrate
Thomas Wouters477c8d52006-05-27 19:21:47 +0000140 t = clock()
141 calibrate()
142 offset = clock() - t
143 if cruns:
144 for i in range(cruns-1):
145 t = clock()
146 calibrate()
147 t = clock() - t
148 if t < offset:
149 offset = t
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000150 # now the real thing
Thomas Wouters477c8d52006-05-27 19:21:47 +0000151 t = clock()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000152 test()
153 t = clock() - t
Thomas Wouters477c8d52006-05-27 19:21:47 +0000154 if t < 0.01:
155 sys.exit("Lower warp required: test times < 10 ms are unreliable")
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000156 self.last_timing = (t-offset,t,offset)
157 self.times.append(t-offset)
158
159 def calibrate(self):
160
Thomas Wouters477c8d52006-05-27 19:21:47 +0000161 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000162
Thomas Wouters477c8d52006-05-27 19:21:47 +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.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000167
168 """
169 return
170
171 def test(self):
172
Thomas Wouters477c8d52006-05-27 19:21:47 +0000173 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000174
Thomas Wouters477c8d52006-05-27 19:21:47 +0000175 The test needs to run self.rounds executing
176 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000177
178 """
179 # do some tests
180 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000181
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000182 def stat(self):
183
Thomas Wouters477c8d52006-05-27 19:21:47 +0000184 """ Returns four values:
185 minimum round time
186 average time per round
187 average time per operation
188 average overhead time
189
190 XXX Should this take warp factors into account?
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000191 """
192 runs = len(self.times)
193 if runs == 0:
194 return 0,0
Thomas Wouters477c8d52006-05-27 19:21:47 +0000195 mintime = min(self.times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000205 return mintime, avg, op_avg, ov_avg
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000224
225 def __init__(self):
226
227 self.tests = {}
228 self.version = 0.31
229
Thomas Wouters477c8d52006-05-27 19:21:47 +0000230 def load_tests(self, setupmod, warp=1, limitnames="", verbose=0):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000231
232 self.warp = warp
Thomas Wouters477c8d52006-05-27 19:21:47 +0000233 if limitnames:
234 limitnames = re.compile(limitnames, re.I)
235 else:
236 limitnames = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000237 tests = self.tests
Thomas Wouters477c8d52006-05-27 19:21:47 +0000238 if verbose:
239 print 'Searching for tests ...',
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000240 setupmod.__dict__.values()
241 for c in setupmod.__dict__.values():
Thomas Wouters477c8d52006-05-27 19:21:47 +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)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000250 l = tests.keys()
251 l.sort()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000252 if verbose:
253 print
254 for t in l:
255 print ' ', t
256 print len(l), "tests found"
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000257 print
258
Thomas Wouters477c8d52006-05-27 19:21:47 +0000259 def run(self, verbose, cruns):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000260
261 tests = self.tests.items()
262 tests.sort()
263 clock = time.clock
Thomas Wouters477c8d52006-05-27 19:21:47 +0000264 print 'Running %i round(s) of the suite at warp factor %i:' % (self.rounds, self.warp)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000265 print
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000266 roundtime = clock()
267 for i in range(self.rounds):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000268 roundstarttime = clock()
269 if verbose:
270 print ' Round %-25i real abs overhead' % (i+1)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000271 for j in range(len(tests)):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000272 name, t = tests[j]
273 if verbose:
274 print '%30s:' % name,
275 t.run(cruns)
276 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))
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000285 self.roundtime = (clock() - roundtime) / self.rounds
286 print
Thomas Wouters477c8d52006-05-27 19:21:47 +0000287
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000288 def print_stat(self, compare_to=None, hidenoise=0):
289
290 if not compare_to:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000291 print '%-30s min run avg run per oprn overhead' % 'Tests:'
292 print '-'*77
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000293 tests = self.tests.items()
294 tests.sort()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000295 totalmintime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000296 for name,t in tests:
Thomas Wouters477c8d52006-05-27 19:21:47 +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)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000304
305 else:
Thomas Wouters477c8d52006-05-27 19:21:47 +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' % \
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000309 'Tests:'
Thomas Wouters477c8d52006-05-27 19:21:47 +0000310 print '-'*77
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000311 tests = self.tests.items()
312 tests.sort()
313 compatible = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000314 totalmintime = other_totalmintime = 0
315 for name, t in tests:
316 mintime, avg, op_avg, ov_avg = t.stat()
317 totalmintime += mintime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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:
Thomas Wouters477c8d52006-05-27 19:21:47 +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
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000327 if hidenoise and abs(qop_avg) < 10:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000328 diff = ''
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000329 else:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000330 diff = '%+7.2f%%' % diff
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000331 else:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000332 qavg, diff = 'n/a', 'n/a'
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000333 compatible = 0
Thomas Wouters477c8d52006-05-27 19:21:47 +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 #
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000340 if compatible and compare_to.roundtime > 0 and \
341 compare_to.version == self.version:
Thomas Wouters477c8d52006-05-27 19:21:47 +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)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000347 else:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000348 print '%30s: %9.2f ms n/a' % \
349 ('Notional minimum round time', totalmintime * 1000.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000380 SwitchOption('-v','verbose output (not recommended)', 0),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000381 SwitchOption('--no-gc','disable garbage collection', 0),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000382 SwitchOption('--no-syscheck',
383 '"disable" sys check interval (set to sys.maxint)', 0),
384 ArgumentOption('-t', 'tests containing substring', ''),
385 ArgumentOption('-C', 'number of calibration runs', 20)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000426 limitnames = self.values['-t']
427 verbose = self.verbose
428 nosyscheck = self.values['--no-syscheck']
429 cruns = self.values['-C']
430 print "CRUNS:", cruns
431
432 print 'PYBENCH',__version__
433
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000443 print 'NO GC'
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000444
Thomas Wouters477c8d52006-05-27 19:21:47 +0000445 # maximise sys check interval
446 if nosyscheck:
447 sys.setcheckinterval(sys.maxint)
448 print 'CHECKINTERVAL =', sys.maxint
449
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters477c8d52006-05-27 19:21:47 +0000465 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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
Thomas Wouters477c8d52006-05-27 19:21:47 +0000494 bench.load_tests(Setup, warp, limitnames, verbose)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000495 try:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000496 bench.run(verbose, cruns)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +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()