blob: d2da6098627ff3e9893931c77923c4d47bbea034 [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
Marc-André Lemburga50e6232007-01-13 22:59:36 +000037import sys, time, operator, string, platform
Marc-André Lemburgc311f642006-04-19 15:27:33 +000038from CommandLine import *
39
40try:
41 import cPickle
42 pickle = cPickle
43except ImportError:
44 import pickle
45
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +000046# Version number; version history: see README file !
47__version__ = '2.0'
48
49### Constants
50
51# Second fractions
52MILLI_SECONDS = 1e3
53MICRO_SECONDS = 1e6
54
55# Percent unit
56PERCENT = 100
57
58# Horizontal line length
59LINE = 79
60
61# Minimum test run-time
62MIN_TEST_RUNTIME = 1e-3
63
64# Number of calibration runs to use for calibrating the tests
65CALIBRATION_RUNS = 20
66
67# Number of calibration loops to run for each calibration run
68CALIBRATION_LOOPS = 20
69
70# Allow skipping calibration ?
71ALLOW_SKIPPING_CALIBRATION = 1
72
73# Timer types
74TIMER_TIME_TIME = 'time.time'
75TIMER_TIME_CLOCK = 'time.clock'
76TIMER_SYSTIMES_PROCESSTIME = 'systimes.processtime'
77
78# Choose platform default timer
79if sys.platform[:3] == 'win':
80 # On WinXP this has 2.5ms resolution
81 TIMER_PLATFORM_DEFAULT = TIMER_TIME_CLOCK
82else:
83 # On Linux this has 1ms resolution
84 TIMER_PLATFORM_DEFAULT = TIMER_TIME_TIME
85
86# Print debug information ?
87_debug = 0
88
89### Helpers
90
91def get_timer(timertype):
92
93 if timertype == TIMER_TIME_TIME:
94 return time.time
95 elif timertype == TIMER_TIME_CLOCK:
96 return time.clock
97 elif timertype == TIMER_SYSTIMES_PROCESSTIME:
98 import systimes
99 return systimes.processtime
100 else:
101 raise TypeError('unknown timer type: %s' % timertype)
102
103def get_machine_details():
104
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000105 if _debug:
106 print 'Getting machine details...'
107 buildno, builddate = platform.python_build()
108 python = platform.python_version()
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000109 try:
110 unichr(100000)
111 except ValueError:
112 # UCS2 build (standard)
113 unicode = 'UCS2'
114 except NameError:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000115 unicode = None
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000116 else:
117 # UCS4 build (most recent Linux distros)
118 unicode = 'UCS4'
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000119 bits, linkage = platform.architecture()
120 return {
121 'platform': platform.platform(),
122 'processor': platform.processor(),
123 'executable': sys.executable,
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000124 'implementation': platform.python_implementation(),
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000125 'python': platform.python_version(),
126 'compiler': platform.python_compiler(),
127 'buildno': buildno,
128 'builddate': builddate,
129 'unicode': unicode,
130 'bits': bits,
131 }
132
133def print_machine_details(d, indent=''):
134
135 l = ['Machine Details:',
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000136 ' Platform ID: %s' % d.get('platform', 'n/a'),
137 ' Processor: %s' % d.get('processor', 'n/a'),
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000138 '',
139 'Python:',
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000140 ' Implementation: %s' % d.get('implementation', 'n/a'),
141 ' Executable: %s' % d.get('executable', 'n/a'),
142 ' Version: %s' % d.get('python', 'n/a'),
143 ' Compiler: %s' % d.get('compiler', 'n/a'),
144 ' Bits: %s' % d.get('bits', 'n/a'),
145 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
146 d.get('buildno', 'n/a')),
147 ' Unicode: %s' % d.get('unicode', 'n/a'),
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000148 ]
149 print indent + string.join(l, '\n' + indent) + '\n'
150
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000151### Test baseclass
152
153class Test:
154
155 """ All test must have this class as baseclass. It provides
156 the necessary interface to the benchmark machinery.
157
158 The tests must set .rounds to a value high enough to let the
159 test run between 20-50 seconds. This is needed because
160 clock()-timing only gives rather inaccurate values (on Linux,
161 for example, it is accurate to a few hundreths of a
162 second). If you don't want to wait that long, use a warp
163 factor larger than 1.
164
165 It is also important to set the .operations variable to a
166 value representing the number of "virtual operations" done per
167 call of .run().
168
169 If you change a test in some way, don't forget to increase
170 it's version number.
171
172 """
173
174 ### Instance variables that each test should override
175
176 # Version number of the test as float (x.yy); this is important
177 # for comparisons of benchmark runs - tests with unequal version
178 # number will not get compared.
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000179 version = 2.0
Tim Petersf9cc5942006-04-21 16:34:54 +0000180
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000181 # The number of abstract operations done in each round of the
182 # test. An operation is the basic unit of what you want to
183 # measure. The benchmark will output the amount of run-time per
184 # operation. Note that in order to raise the measured timings
185 # significantly above noise level, it is often required to repeat
186 # sets of operations more than once per test round. The measured
187 # overhead per test round should be less than 1 second.
188 operations = 1
189
190 # Number of rounds to execute per test run. This should be
191 # adjusted to a figure that results in a test run-time of between
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000192 # 1-2 seconds.
193 rounds = 100000
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000194
195 ### Internal variables
196
197 # Mark this class as implementing a test
198 is_a_test = 1
199
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000200 # Last timing: (real, run, overhead)
201 last_timing = (0.0, 0.0, 0.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000202
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000203 # Warp factor to use for this test
204 warp = 1
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000205
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000206 # Number of calibration runs to use
207 calibration_runs = CALIBRATION_RUNS
208
209 # List of calibration timings
210 overhead_times = None
211
212 # List of test run timings
213 times = []
214
215 # Timer used for the benchmark
216 timer = TIMER_PLATFORM_DEFAULT
217
218 def __init__(self, warp=None, calibration_runs=None, timer=None):
219
220 # Set parameters
221 if warp is not None:
222 self.rounds = int(self.rounds / warp)
Steve Holden431a7632006-05-26 16:27:59 +0000223 if self.rounds == 0:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000224 raise ValueError('warp factor set too high')
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000225 self.warp = warp
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000226 if calibration_runs is not None:
227 if (not ALLOW_SKIPPING_CALIBRATION and
228 calibration_runs < 1):
229 raise ValueError('at least one calibration run is required')
230 self.calibration_runs = calibration_runs
231 if timer is not None:
232 timer = timer
233
234 # Init variables
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000235 self.times = []
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000236 self.overhead_times = []
237
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000238 # We want these to be in the instance dict, so that pickle
239 # saves them
240 self.version = self.version
241 self.operations = self.operations
242 self.rounds = self.rounds
243
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000244 def get_timer(self):
245
246 """ Return the timer function to use for the test.
247
248 """
249 return get_timer(self.timer)
250
251 def compatible(self, other):
252
253 """ Return 1/0 depending on whether the test is compatible
254 with the other Test instance or not.
255
256 """
257 if self.version != other.version:
258 return 0
259 if self.rounds != other.rounds:
260 return 0
261 return 1
262
263 def calibrate_test(self):
264
265 if self.calibration_runs == 0:
266 self.overhead_times = [0.0]
267 return
268
269 calibrate = self.calibrate
270 timer = self.get_timer()
271 calibration_loops = range(CALIBRATION_LOOPS)
272
273 # Time the calibration loop overhead
274 prep_times = []
275 for i in range(self.calibration_runs):
276 t = timer()
277 for i in calibration_loops:
278 pass
279 t = timer() - t
280 prep_times.append(t)
281 min_prep_time = min(prep_times)
282 if _debug:
283 print
284 print 'Calib. prep time = %.6fms' % (
285 min_prep_time * MILLI_SECONDS)
286
287 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
288 # .calibrate() method calls each)
289 for i in range(self.calibration_runs):
290 t = timer()
291 for i in calibration_loops:
292 calibrate()
293 t = timer() - t
294 self.overhead_times.append(t / CALIBRATION_LOOPS
295 - min_prep_time)
296
297 # Check the measured times
298 min_overhead = min(self.overhead_times)
299 max_overhead = max(self.overhead_times)
300 if _debug:
301 print 'Calib. overhead time = %.6fms' % (
302 min_overhead * MILLI_SECONDS)
303 if min_overhead < 0.0:
304 raise ValueError('calibration setup did not work')
305 if max_overhead - min_overhead > 0.1:
306 raise ValueError(
307 'overhead calibration timing range too inaccurate: '
308 '%r - %r' % (min_overhead, max_overhead))
309
310 def run(self):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000311
312 """ Run the test in two phases: first calibrate, then
313 do the actual test. Be careful to keep the calibration
314 timing low w/r to the test timing.
Tim Petersf9cc5942006-04-21 16:34:54 +0000315
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000316 """
317 test = self.test
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000318 timer = self.get_timer()
319
320 # Get calibration
321 min_overhead = min(self.overhead_times)
322
323 # Test run
324 t = timer()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000325 test()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000326 t = timer() - t
327 if t < MIN_TEST_RUNTIME:
328 raise ValueError('warp factor too high: '
329 'test times are < 10ms')
330 eff_time = t - min_overhead
331 if eff_time < 0:
332 raise ValueError('wrong calibration')
333 self.last_timing = (eff_time, t, min_overhead)
334 self.times.append(eff_time)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000335
336 def calibrate(self):
337
Tim Petersf9cc5942006-04-21 16:34:54 +0000338 """ Calibrate the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000339
Tim Petersf9cc5942006-04-21 16:34:54 +0000340 This method should execute everything that is needed to
341 setup and run the test - except for the actual operations
342 that you intend to measure. pybench uses this method to
343 measure the test implementation overhead.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000344
345 """
346 return
347
348 def test(self):
349
Tim Petersf9cc5942006-04-21 16:34:54 +0000350 """ Run the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000351
Tim Petersf9cc5942006-04-21 16:34:54 +0000352 The test needs to run self.rounds executing
353 self.operations number of operations each.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000354
355 """
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000356 return
Tim Petersf9cc5942006-04-21 16:34:54 +0000357
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000358 def stat(self):
359
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000360 """ Return test run statistics as tuple:
Tim Petersf9cc5942006-04-21 16:34:54 +0000361
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000362 (minimum run time,
363 average run time,
364 total run time,
365 average time per operation,
366 minimum overhead time)
367
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000368 """
369 runs = len(self.times)
370 if runs == 0:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000371 return 0.0, 0.0, 0.0, 0.0
372 min_time = min(self.times)
373 total_time = reduce(operator.add, self.times, 0.0)
374 avg_time = total_time / float(runs)
375 operation_avg = total_time / float(runs
376 * self.rounds
377 * self.operations)
378 if self.overhead_times:
379 min_overhead = min(self.overhead_times)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000380 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000381 min_overhead = self.last_timing[2]
382 return min_time, avg_time, total_time, operation_avg, min_overhead
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000383
384### Load Setup
385
386# This has to be done after the definition of the Test class, since
387# the Setup module will import subclasses using this class.
388
389import Setup
390
391### Benchmark base class
392
393class Benchmark:
394
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000395 # Name of the benchmark
396 name = ''
397
398 # Number of benchmark rounds to run
399 rounds = 1
400
401 # Warp factor use to run the tests
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000402 warp = 1 # Warp factor
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000403
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000404 # Average benchmark round time
405 roundtime = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000406
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000407 # Benchmark version number as float x.yy
408 version = 2.0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000409
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000410 # Produce verbose output ?
411 verbose = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000412
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000413 # Dictionary with the machine details
414 machine_details = None
415
416 # Timer used for the benchmark
417 timer = TIMER_PLATFORM_DEFAULT
418
419 def __init__(self, name, verbose=None, timer=None, warp=None,
420 calibration_runs=None):
Marc-André Lemburg3b3f1182006-06-13 19:20:07 +0000421
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000422 if name:
423 self.name = name
Steve Holden431a7632006-05-26 16:27:59 +0000424 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000425 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
426 (time.localtime(time.time())[:6])
427 if verbose is not None:
428 self.verbose = verbose
429 if timer is not None:
430 self.timer = timer
431 if warp is not None:
432 self.warp = warp
433 if calibration_runs is not None:
434 self.calibration_runs = calibration_runs
435
436 # Init vars
437 self.tests = {}
438 if _debug:
439 print 'Getting machine details...'
440 self.machine_details = get_machine_details()
Marc-André Lemburg3b3f1182006-06-13 19:20:07 +0000441
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000442 # Make .version an instance attribute to have it saved in the
443 # Benchmark pickle
444 self.version = self.version
445
446 def get_timer(self):
447
448 """ Return the timer function to use for the test.
449
450 """
451 return get_timer(self.timer)
452
453 def compatible(self, other):
454
455 """ Return 1/0 depending on whether the benchmark is
456 compatible with the other Benchmark instance or not.
457
458 """
459 if self.version != other.version:
460 return 0
461 if (self.machine_details == other.machine_details and
462 self.timer != other.timer):
463 return 0
464 if (self.calibration_runs == 0 and
465 other.calibration_runs != 0):
466 return 0
467 if (self.calibration_runs != 0 and
468 other.calibration_runs == 0):
469 return 0
470 return 1
471
472 def load_tests(self, setupmod, limitnames=None):
473
474 # Add tests
475 if self.verbose:
476 print 'Searching for tests ...'
477 print '--------------------------------------'
478 for testclass in setupmod.__dict__.values():
479 if not hasattr(testclass, 'is_a_test'):
Steve Holden431a7632006-05-26 16:27:59 +0000480 continue
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000481 name = testclass.__name__
Steve Holden431a7632006-05-26 16:27:59 +0000482 if name == 'Test':
483 continue
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000484 if (limitnames is not None and
485 limitnames.search(name) is None):
Steve Holden431a7632006-05-26 16:27:59 +0000486 continue
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000487 self.tests[name] = testclass(
488 warp=self.warp,
489 calibration_runs=self.calibration_runs,
490 timer=self.timer)
491 l = self.tests.keys()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000492 l.sort()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000493 if self.verbose:
494 for name in l:
495 print ' %s' % name
496 print '--------------------------------------'
497 print ' %i tests found' % len(l)
Steve Holden431a7632006-05-26 16:27:59 +0000498 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000499
500 def calibrate(self):
501
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000502 print 'Calibrating tests. Please wait...',
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000503 if self.verbose:
504 print
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000505 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000506 print 'Test min max'
507 print '-' * LINE
508 tests = self.tests.items()
509 tests.sort()
510 for i in range(len(tests)):
511 name, test = tests[i]
512 test.calibrate_test()
513 if self.verbose:
514 print '%30s: %6.3fms %6.3fms' % \
515 (name,
516 min(test.overhead_times) * MILLI_SECONDS,
517 max(test.overhead_times) * MILLI_SECONDS)
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000518 if self.verbose:
519 print
520 print 'Done with the calibration.'
521 else:
522 print 'done.'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000523 print
524
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000525 def run(self):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000526
527 tests = self.tests.items()
528 tests.sort()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000529 timer = self.get_timer()
530 print 'Running %i round(s) of the suite at warp factor %i:' % \
531 (self.rounds, self.warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000532 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000533 self.roundtimes = []
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000534 for i in range(self.rounds):
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000535 if self.verbose:
536 print ' Round %-25i effective absolute overhead' % (i+1)
537 total_eff_time = 0.0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000538 for j in range(len(tests)):
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000539 name, test = tests[j]
540 if self.verbose:
Steve Holden431a7632006-05-26 16:27:59 +0000541 print '%30s:' % name,
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000542 test.run()
543 (eff_time, abs_time, min_overhead) = test.last_timing
544 total_eff_time = total_eff_time + eff_time
545 if self.verbose:
546 print ' %5.0fms %5.0fms %7.3fms' % \
547 (eff_time * MILLI_SECONDS,
548 abs_time * MILLI_SECONDS,
549 min_overhead * MILLI_SECONDS)
550 self.roundtimes.append(total_eff_time)
551 if self.verbose:
552 print (' '
553 ' ------------------------------')
554 print (' '
555 ' Totals: %6.0fms' %
556 (total_eff_time * MILLI_SECONDS))
Steve Holden431a7632006-05-26 16:27:59 +0000557 print
558 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000559 print '* Round %i done in %.3f seconds.' % (i+1,
560 total_eff_time)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000561 print
Tim Petersf9cc5942006-04-21 16:34:54 +0000562
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000563 def stat(self):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000564
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000565 """ Return benchmark run statistics as tuple:
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000566
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000567 (minimum round time,
568 average round time,
569 maximum round time)
570
571 XXX Currently not used, since the benchmark does test
572 statistics across all rounds.
573
574 """
575 runs = len(self.roundtimes)
576 if runs == 0:
577 return 0.0, 0.0
578 min_time = min(self.roundtimes)
579 total_time = reduce(operator.add, self.roundtimes, 0.0)
580 avg_time = total_time / float(runs)
581 max_time = max(self.roundtimes)
582 return (min_time, avg_time, max_time)
583
584 def print_header(self, title='Benchmark'):
585
586 print '-' * LINE
587 print '%s: %s' % (title, self.name)
588 print '-' * LINE
589 print
590 print ' Rounds: %s' % self.rounds
591 print ' Warp: %s' % self.warp
592 print ' Timer: %s' % self.timer
593 print
594 if self.machine_details:
595 print_machine_details(self.machine_details, indent=' ')
596 print
597
598 def print_benchmark(self, hidenoise=0, limitnames=None):
599
600 print ('Test '
601 ' minimum average operation overhead')
602 print '-' * LINE
603 tests = self.tests.items()
604 tests.sort()
605 total_min_time = 0.0
606 total_avg_time = 0.0
607 for name, test in tests:
608 if (limitnames is not None and
609 limitnames.search(name) is None):
610 continue
611 (min_time,
612 avg_time,
613 total_time,
614 op_avg,
615 min_overhead) = test.stat()
616 total_min_time = total_min_time + min_time
617 total_avg_time = total_avg_time + avg_time
618 print '%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
619 (name,
620 min_time * MILLI_SECONDS,
621 avg_time * MILLI_SECONDS,
622 op_avg * MICRO_SECONDS,
623 min_overhead *MILLI_SECONDS)
624 print '-' * LINE
625 print ('Totals: '
626 ' %6.0fms %6.0fms' %
627 (total_min_time * MILLI_SECONDS,
628 total_avg_time * MILLI_SECONDS,
629 ))
630 print
631
632 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
633
634 # Check benchmark versions
635 if compare_to.version != self.version:
636 print ('* Benchmark versions differ: '
637 'cannot compare this benchmark to "%s" !' %
638 compare_to.name)
639 print
640 self.print_benchmark(hidenoise=hidenoise,
641 limitnames=limitnames)
642 return
643
644 # Print header
645 compare_to.print_header('Comparing with')
646 print ('Test '
647 ' minimum run-time average run-time')
648 print (' '
649 ' this other diff this other diff')
650 print '-' * LINE
651
652 # Print test comparisons
653 tests = self.tests.items()
654 tests.sort()
655 total_min_time = other_total_min_time = 0.0
656 total_avg_time = other_total_avg_time = 0.0
657 benchmarks_compatible = self.compatible(compare_to)
658 tests_compatible = 1
659 for name, test in tests:
660 if (limitnames is not None and
661 limitnames.search(name) is None):
662 continue
663 (min_time,
664 avg_time,
665 total_time,
666 op_avg,
667 min_overhead) = test.stat()
668 total_min_time = total_min_time + min_time
669 total_avg_time = total_avg_time + avg_time
670 try:
671 other = compare_to.tests[name]
672 except KeyError:
673 other = None
674 if other is None:
675 # Other benchmark doesn't include the given test
676 min_diff, avg_diff = 'n/a', 'n/a'
677 other_min_time = 0.0
678 other_avg_time = 0.0
679 tests_compatible = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000680 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000681 (other_min_time,
682 other_avg_time,
683 other_total_time,
684 other_op_avg,
685 other_min_overhead) = other.stat()
686 other_total_min_time = other_total_min_time + other_min_time
687 other_total_avg_time = other_total_avg_time + other_avg_time
688 if (benchmarks_compatible and
689 test.compatible(other)):
690 # Both benchmark and tests are comparible
691 min_diff = ((min_time * self.warp) /
692 (other_min_time * other.warp) - 1.0)
693 avg_diff = ((avg_time * self.warp) /
694 (other_avg_time * other.warp) - 1.0)
695 if hidenoise and abs(min_diff) < 10.0:
696 min_diff = ''
697 else:
698 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
699 if hidenoise and abs(avg_diff) < 10.0:
700 avg_diff = ''
701 else:
702 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
703 else:
704 # Benchmark or tests are not comparible
705 min_diff, avg_diff = 'n/a', 'n/a'
706 tests_compatible = 0
707 print '%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
708 (name,
709 min_time * MILLI_SECONDS,
710 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
711 min_diff,
712 avg_time * MILLI_SECONDS,
713 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
714 avg_diff)
715 print '-' * LINE
716
717 # Summarise test results
718 if not benchmarks_compatible or not tests_compatible:
719 min_diff, avg_diff = 'n/a', 'n/a'
720 else:
721 if other_total_min_time != 0.0:
722 min_diff = '%+5.1f%%' % (
723 ((total_min_time * self.warp) /
724 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
725 else:
726 min_diff = 'n/a'
727 if other_total_avg_time != 0.0:
728 avg_diff = '%+5.1f%%' % (
729 ((total_avg_time * self.warp) /
730 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
731 else:
732 avg_diff = 'n/a'
733 print ('Totals: '
734 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
735 (total_min_time * MILLI_SECONDS,
736 (other_total_min_time * compare_to.warp/self.warp
737 * MILLI_SECONDS),
738 min_diff,
739 total_avg_time * MILLI_SECONDS,
740 (other_total_avg_time * compare_to.warp/self.warp
741 * MILLI_SECONDS),
742 avg_diff
743 ))
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000744 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000745 print '(this=%s, other=%s)' % (self.name,
746 compare_to.name)
747 print
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000748
749class PyBenchCmdline(Application):
750
751 header = ("PYBENCH - a benchmark test suite for Python "
752 "interpreters/compilers.")
753
754 version = __version__
755
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000756 debug = _debug
757
758 options = [ArgumentOption('-n',
759 'number of rounds',
760 Setup.Number_of_rounds),
761 ArgumentOption('-f',
762 'save benchmark to file arg',
763 ''),
764 ArgumentOption('-c',
765 'compare benchmark with the one in file arg',
766 ''),
767 ArgumentOption('-s',
768 'show benchmark in file arg, then exit',
769 ''),
770 ArgumentOption('-w',
771 'set warp factor to arg',
772 Setup.Warp_factor),
773 ArgumentOption('-t',
774 'run only tests with names matching arg',
775 ''),
776 ArgumentOption('-C',
777 'set the number of calibration runs to arg',
778 CALIBRATION_RUNS),
779 SwitchOption('-d',
780 'hide noise in comparisons',
781 0),
782 SwitchOption('-v',
783 'verbose output (not recommended)',
784 0),
785 SwitchOption('--with-gc',
786 'enable garbage collection',
787 0),
788 SwitchOption('--with-syscheck',
789 'use default sys check interval',
790 0),
791 ArgumentOption('--timer',
792 'use given timer',
793 TIMER_PLATFORM_DEFAULT),
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000794 ]
795
796 about = """\
797The normal operation is to run the suite and display the
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000798results. Use -f to save them for later reuse or comparisons.
799
800Available timers:
801
802 time.time
803 time.clock
804 systimes.processtime
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000805
806Examples:
807
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000808python2.1 pybench.py -f p21.pybench
809python2.5 pybench.py -f p25.pybench
810python pybench.py -s p25.pybench -c p21.pybench
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000811"""
812 copyright = __copyright__
813
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000814 def main(self):
815
816 rounds = self.values['-n']
817 reportfile = self.values['-f']
818 show_bench = self.values['-s']
819 compare_to = self.values['-c']
820 hidenoise = self.values['-d']
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000821 warp = int(self.values['-w'])
822 withgc = self.values['--with-gc']
Steve Holden431a7632006-05-26 16:27:59 +0000823 limitnames = self.values['-t']
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000824 if limitnames:
825 if _debug:
826 print '* limiting test names to one with substring "%s"' % \
827 limitnames
828 limitnames = re.compile(limitnames, re.I)
829 else:
830 limitnames = None
Steve Holden431a7632006-05-26 16:27:59 +0000831 verbose = self.verbose
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000832 withsyscheck = self.values['--with-syscheck']
833 calibration_runs = self.values['-C']
834 timer = self.values['--timer']
Tim Peters19bfd422006-05-26 21:51:13 +0000835
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000836 print '-' * LINE
837 print 'PYBENCH %s' % __version__
838 print '-' * LINE
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000839 print '* using %s %s' % (
840 platform.python_implementation(),
841 string.join(string.split(sys.version), ' '))
Tim Petersf9cc5942006-04-21 16:34:54 +0000842
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000843 # Switch off garbage collection
844 if not withgc:
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000845 try:
846 import gc
847 except ImportError:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000848 print '* Python version doesn\'t support garbage collection'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000849 else:
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000850 try:
851 gc.disable()
852 except NotImplementedError:
853 print '* Python version doesn\'t support gc.disable'
854 else:
855 print '* disabled garbage collection'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000856
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000857 # "Disable" sys check interval
858 if not withsyscheck:
859 # Too bad the check interval uses an int instead of a long...
860 value = 2147483647
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000861 try:
862 sys.setcheckinterval(value)
Marc-André Lemburgb1a8ef62007-01-13 23:15:33 +0000863 except (AttributeError, NotImplementedError):
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000864 print '* Python version doesn\'t support sys.setcheckinterval'
865 else:
866 print '* system check interval set to maximum: %s' % value
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000867
868 if timer == TIMER_SYSTIMES_PROCESSTIME:
869 import systimes
870 print '* using timer: systimes.processtime (%s)' % \
871 systimes.SYSTIMES_IMPLEMENTATION
872 else:
873 print '* using timer: %s' % timer
Steve Holden431a7632006-05-26 16:27:59 +0000874
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000875 print
876
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000877 if compare_to:
878 try:
879 f = open(compare_to,'rb')
880 bench = pickle.load(f)
881 bench.name = compare_to
882 f.close()
883 compare_to = bench
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000884 except IOError, reason:
885 print '* Error opening/reading file %s: %s' % (
886 repr(compare_to),
887 reason)
Tim Petersf9cc5942006-04-21 16:34:54 +0000888 compare_to = None
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000889
890 if show_bench:
891 try:
892 f = open(show_bench,'rb')
893 bench = pickle.load(f)
894 bench.name = show_bench
895 f.close()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000896 bench.print_header()
897 if compare_to:
898 bench.print_comparison(compare_to,
899 hidenoise=hidenoise,
900 limitnames=limitnames)
901 else:
902 bench.print_benchmark(hidenoise=hidenoise,
903 limitnames=limitnames)
Marc-André Lemburgf6fc4542006-08-29 10:34:12 +0000904 except IOError, reason:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000905 print '* Error opening/reading file %s: %s' % (
906 repr(show_bench),
907 reason)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000908 print
909 return
910
911 if reportfile:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000912 print 'Creating benchmark: %s (rounds=%i, warp=%i)' % \
913 (reportfile, rounds, warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000914 print
915
916 # Create benchmark object
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000917 bench = Benchmark(reportfile,
918 verbose=verbose,
919 timer=timer,
920 warp=warp,
921 calibration_runs=calibration_runs)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000922 bench.rounds = rounds
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000923 bench.load_tests(Setup, limitnames=limitnames)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000924 try:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000925 bench.calibrate()
926 bench.run()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000927 except KeyboardInterrupt:
928 print
929 print '*** KeyboardInterrupt -- Aborting'
930 print
931 return
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000932 bench.print_header()
933 if compare_to:
934 bench.print_comparison(compare_to,
935 hidenoise=hidenoise,
936 limitnames=limitnames)
937 else:
938 bench.print_benchmark(hidenoise=hidenoise,
939 limitnames=limitnames)
940
941 # Ring bell
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000942 sys.stderr.write('\007')
943
944 if reportfile:
945 try:
946 f = open(reportfile,'wb')
947 bench.name = reportfile
948 pickle.dump(bench,f)
949 f.close()
Marc-André Lemburgf6fc4542006-08-29 10:34:12 +0000950 except IOError, reason:
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000951 print '* Error opening/writing reportfile'
Marc-André Lemburgf6fc4542006-08-29 10:34:12 +0000952 except IOError, reason:
953 print '* Error opening/writing reportfile %s: %s' % (
954 reportfile,
955 reason)
956 print
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000957
958if __name__ == '__main__':
959 PyBenchCmdline()