blob: e1bc95d05d83f1a7dbb729d9837e0f690ba3f58e [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,
Jeffrey Yasskin3accbb02008-03-08 21:35:15 +0000124 'implementation': getattr(platform, 'python_implementation',
125 lambda:'n/a')(),
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000126 'python': platform.python_version(),
127 'compiler': platform.python_compiler(),
128 'buildno': buildno,
129 'builddate': builddate,
130 'unicode': unicode,
131 'bits': bits,
132 }
133
134def print_machine_details(d, indent=''):
135
136 l = ['Machine Details:',
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000137 ' Platform ID: %s' % d.get('platform', 'n/a'),
138 ' Processor: %s' % d.get('processor', 'n/a'),
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000139 '',
140 'Python:',
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000141 ' Implementation: %s' % d.get('implementation', 'n/a'),
142 ' Executable: %s' % d.get('executable', 'n/a'),
143 ' Version: %s' % d.get('python', 'n/a'),
144 ' Compiler: %s' % d.get('compiler', 'n/a'),
145 ' Bits: %s' % d.get('bits', 'n/a'),
146 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
147 d.get('buildno', 'n/a')),
148 ' Unicode: %s' % d.get('unicode', 'n/a'),
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000149 ]
150 print indent + string.join(l, '\n' + indent) + '\n'
151
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000152### Test baseclass
153
154class Test:
155
156 """ All test must have this class as baseclass. It provides
157 the necessary interface to the benchmark machinery.
158
159 The tests must set .rounds to a value high enough to let the
160 test run between 20-50 seconds. This is needed because
161 clock()-timing only gives rather inaccurate values (on Linux,
162 for example, it is accurate to a few hundreths of a
163 second). If you don't want to wait that long, use a warp
164 factor larger than 1.
165
166 It is also important to set the .operations variable to a
167 value representing the number of "virtual operations" done per
168 call of .run().
169
170 If you change a test in some way, don't forget to increase
Georg Brandlfd611072007-03-09 12:58:41 +0000171 its version number.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000172
173 """
174
175 ### Instance variables that each test should override
176
177 # Version number of the test as float (x.yy); this is important
178 # for comparisons of benchmark runs - tests with unequal version
179 # number will not get compared.
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000180 version = 2.0
Tim Petersf9cc5942006-04-21 16:34:54 +0000181
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000182 # The number of abstract operations done in each round of the
183 # test. An operation is the basic unit of what you want to
184 # measure. The benchmark will output the amount of run-time per
185 # operation. Note that in order to raise the measured timings
186 # significantly above noise level, it is often required to repeat
187 # sets of operations more than once per test round. The measured
188 # overhead per test round should be less than 1 second.
189 operations = 1
190
191 # Number of rounds to execute per test run. This should be
192 # adjusted to a figure that results in a test run-time of between
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000193 # 1-2 seconds.
194 rounds = 100000
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000195
196 ### Internal variables
197
198 # Mark this class as implementing a test
199 is_a_test = 1
200
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000201 # Last timing: (real, run, overhead)
202 last_timing = (0.0, 0.0, 0.0)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000203
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000204 # Warp factor to use for this test
205 warp = 1
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000206
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000207 # Number of calibration runs to use
208 calibration_runs = CALIBRATION_RUNS
209
210 # List of calibration timings
211 overhead_times = None
212
213 # List of test run timings
214 times = []
215
216 # Timer used for the benchmark
217 timer = TIMER_PLATFORM_DEFAULT
218
219 def __init__(self, warp=None, calibration_runs=None, timer=None):
220
221 # Set parameters
222 if warp is not None:
223 self.rounds = int(self.rounds / warp)
Steve Holden431a7632006-05-26 16:27:59 +0000224 if self.rounds == 0:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000225 raise ValueError('warp factor set too high')
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000226 self.warp = warp
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000227 if calibration_runs is not None:
228 if (not ALLOW_SKIPPING_CALIBRATION and
229 calibration_runs < 1):
230 raise ValueError('at least one calibration run is required')
231 self.calibration_runs = calibration_runs
232 if timer is not None:
233 timer = timer
234
235 # Init variables
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000236 self.times = []
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000237 self.overhead_times = []
238
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000239 # We want these to be in the instance dict, so that pickle
240 # saves them
241 self.version = self.version
242 self.operations = self.operations
243 self.rounds = self.rounds
244
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000245 def get_timer(self):
246
247 """ Return the timer function to use for the test.
248
249 """
250 return get_timer(self.timer)
251
252 def compatible(self, other):
253
254 """ Return 1/0 depending on whether the test is compatible
255 with the other Test instance or not.
256
257 """
258 if self.version != other.version:
259 return 0
260 if self.rounds != other.rounds:
261 return 0
262 return 1
263
264 def calibrate_test(self):
265
266 if self.calibration_runs == 0:
267 self.overhead_times = [0.0]
268 return
269
270 calibrate = self.calibrate
271 timer = self.get_timer()
272 calibration_loops = range(CALIBRATION_LOOPS)
273
274 # Time the calibration loop overhead
275 prep_times = []
276 for i in range(self.calibration_runs):
277 t = timer()
278 for i in calibration_loops:
279 pass
280 t = timer() - t
281 prep_times.append(t)
282 min_prep_time = min(prep_times)
283 if _debug:
284 print
285 print 'Calib. prep time = %.6fms' % (
286 min_prep_time * MILLI_SECONDS)
287
288 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
289 # .calibrate() method calls each)
290 for i in range(self.calibration_runs):
291 t = timer()
292 for i in calibration_loops:
293 calibrate()
294 t = timer() - t
295 self.overhead_times.append(t / CALIBRATION_LOOPS
296 - min_prep_time)
297
298 # Check the measured times
299 min_overhead = min(self.overhead_times)
300 max_overhead = max(self.overhead_times)
301 if _debug:
302 print 'Calib. overhead time = %.6fms' % (
303 min_overhead * MILLI_SECONDS)
304 if min_overhead < 0.0:
305 raise ValueError('calibration setup did not work')
306 if max_overhead - min_overhead > 0.1:
307 raise ValueError(
308 'overhead calibration timing range too inaccurate: '
309 '%r - %r' % (min_overhead, max_overhead))
310
311 def run(self):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000312
313 """ Run the test in two phases: first calibrate, then
314 do the actual test. Be careful to keep the calibration
315 timing low w/r to the test timing.
Tim Petersf9cc5942006-04-21 16:34:54 +0000316
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000317 """
318 test = self.test
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000319 timer = self.get_timer()
320
321 # Get calibration
322 min_overhead = min(self.overhead_times)
323
324 # Test run
325 t = timer()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000326 test()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000327 t = timer() - t
328 if t < MIN_TEST_RUNTIME:
329 raise ValueError('warp factor too high: '
330 'test times are < 10ms')
331 eff_time = t - min_overhead
332 if eff_time < 0:
333 raise ValueError('wrong calibration')
334 self.last_timing = (eff_time, t, min_overhead)
335 self.times.append(eff_time)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000336
337 def calibrate(self):
338
Tim Petersf9cc5942006-04-21 16:34:54 +0000339 """ Calibrate the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000340
Tim Petersf9cc5942006-04-21 16:34:54 +0000341 This method should execute everything that is needed to
342 setup and run the test - except for the actual operations
343 that you intend to measure. pybench uses this method to
344 measure the test implementation overhead.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000345
346 """
347 return
348
349 def test(self):
350
Tim Petersf9cc5942006-04-21 16:34:54 +0000351 """ Run the test.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000352
Tim Petersf9cc5942006-04-21 16:34:54 +0000353 The test needs to run self.rounds executing
354 self.operations number of operations each.
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000355
356 """
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000357 return
Tim Petersf9cc5942006-04-21 16:34:54 +0000358
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000359 def stat(self):
360
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000361 """ Return test run statistics as tuple:
Tim Petersf9cc5942006-04-21 16:34:54 +0000362
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000363 (minimum run time,
364 average run time,
365 total run time,
366 average time per operation,
367 minimum overhead time)
368
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000369 """
370 runs = len(self.times)
371 if runs == 0:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000372 return 0.0, 0.0, 0.0, 0.0
373 min_time = min(self.times)
374 total_time = reduce(operator.add, self.times, 0.0)
375 avg_time = total_time / float(runs)
376 operation_avg = total_time / float(runs
377 * self.rounds
378 * self.operations)
379 if self.overhead_times:
380 min_overhead = min(self.overhead_times)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000381 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000382 min_overhead = self.last_timing[2]
383 return min_time, avg_time, total_time, operation_avg, min_overhead
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000384
385### Load Setup
386
387# This has to be done after the definition of the Test class, since
388# the Setup module will import subclasses using this class.
389
390import Setup
391
392### Benchmark base class
393
394class Benchmark:
395
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000396 # Name of the benchmark
397 name = ''
398
399 # Number of benchmark rounds to run
400 rounds = 1
401
402 # Warp factor use to run the tests
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000403 warp = 1 # Warp factor
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000404
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000405 # Average benchmark round time
406 roundtime = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000407
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000408 # Benchmark version number as float x.yy
409 version = 2.0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000410
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000411 # Produce verbose output ?
412 verbose = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000413
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000414 # Dictionary with the machine details
415 machine_details = None
416
417 # Timer used for the benchmark
418 timer = TIMER_PLATFORM_DEFAULT
419
420 def __init__(self, name, verbose=None, timer=None, warp=None,
421 calibration_runs=None):
Marc-André Lemburg3b3f1182006-06-13 19:20:07 +0000422
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000423 if name:
424 self.name = name
Steve Holden431a7632006-05-26 16:27:59 +0000425 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000426 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
427 (time.localtime(time.time())[:6])
428 if verbose is not None:
429 self.verbose = verbose
430 if timer is not None:
431 self.timer = timer
432 if warp is not None:
433 self.warp = warp
434 if calibration_runs is not None:
435 self.calibration_runs = calibration_runs
436
437 # Init vars
438 self.tests = {}
439 if _debug:
440 print 'Getting machine details...'
441 self.machine_details = get_machine_details()
Marc-André Lemburg3b3f1182006-06-13 19:20:07 +0000442
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000443 # Make .version an instance attribute to have it saved in the
444 # Benchmark pickle
445 self.version = self.version
446
447 def get_timer(self):
448
449 """ Return the timer function to use for the test.
450
451 """
452 return get_timer(self.timer)
453
454 def compatible(self, other):
455
456 """ Return 1/0 depending on whether the benchmark is
457 compatible with the other Benchmark instance or not.
458
459 """
460 if self.version != other.version:
461 return 0
462 if (self.machine_details == other.machine_details and
463 self.timer != other.timer):
464 return 0
465 if (self.calibration_runs == 0 and
466 other.calibration_runs != 0):
467 return 0
468 if (self.calibration_runs != 0 and
469 other.calibration_runs == 0):
470 return 0
471 return 1
472
473 def load_tests(self, setupmod, limitnames=None):
474
475 # Add tests
476 if self.verbose:
477 print 'Searching for tests ...'
478 print '--------------------------------------'
479 for testclass in setupmod.__dict__.values():
480 if not hasattr(testclass, 'is_a_test'):
Steve Holden431a7632006-05-26 16:27:59 +0000481 continue
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000482 name = testclass.__name__
Steve Holden431a7632006-05-26 16:27:59 +0000483 if name == 'Test':
484 continue
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000485 if (limitnames is not None and
486 limitnames.search(name) is None):
Steve Holden431a7632006-05-26 16:27:59 +0000487 continue
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000488 self.tests[name] = testclass(
489 warp=self.warp,
490 calibration_runs=self.calibration_runs,
491 timer=self.timer)
492 l = self.tests.keys()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000493 l.sort()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000494 if self.verbose:
495 for name in l:
496 print ' %s' % name
497 print '--------------------------------------'
498 print ' %i tests found' % len(l)
Steve Holden431a7632006-05-26 16:27:59 +0000499 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000500
501 def calibrate(self):
502
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000503 print 'Calibrating tests. Please wait...',
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000504 if self.verbose:
505 print
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000506 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000507 print 'Test min max'
508 print '-' * LINE
509 tests = self.tests.items()
510 tests.sort()
511 for i in range(len(tests)):
512 name, test = tests[i]
513 test.calibrate_test()
514 if self.verbose:
515 print '%30s: %6.3fms %6.3fms' % \
516 (name,
517 min(test.overhead_times) * MILLI_SECONDS,
518 max(test.overhead_times) * MILLI_SECONDS)
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000519 if self.verbose:
520 print
521 print 'Done with the calibration.'
522 else:
523 print 'done.'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000524 print
525
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000526 def run(self):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000527
528 tests = self.tests.items()
529 tests.sort()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000530 timer = self.get_timer()
531 print 'Running %i round(s) of the suite at warp factor %i:' % \
532 (self.rounds, self.warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000533 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000534 self.roundtimes = []
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000535 for i in range(self.rounds):
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000536 if self.verbose:
537 print ' Round %-25i effective absolute overhead' % (i+1)
538 total_eff_time = 0.0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000539 for j in range(len(tests)):
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000540 name, test = tests[j]
541 if self.verbose:
Steve Holden431a7632006-05-26 16:27:59 +0000542 print '%30s:' % name,
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000543 test.run()
544 (eff_time, abs_time, min_overhead) = test.last_timing
545 total_eff_time = total_eff_time + eff_time
546 if self.verbose:
547 print ' %5.0fms %5.0fms %7.3fms' % \
548 (eff_time * MILLI_SECONDS,
549 abs_time * MILLI_SECONDS,
550 min_overhead * MILLI_SECONDS)
551 self.roundtimes.append(total_eff_time)
552 if self.verbose:
553 print (' '
554 ' ------------------------------')
555 print (' '
556 ' Totals: %6.0fms' %
557 (total_eff_time * MILLI_SECONDS))
Steve Holden431a7632006-05-26 16:27:59 +0000558 print
559 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000560 print '* Round %i done in %.3f seconds.' % (i+1,
561 total_eff_time)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000562 print
Tim Petersf9cc5942006-04-21 16:34:54 +0000563
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000564 def stat(self):
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000565
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000566 """ Return benchmark run statistics as tuple:
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000567
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000568 (minimum round time,
569 average round time,
570 maximum round time)
571
572 XXX Currently not used, since the benchmark does test
573 statistics across all rounds.
574
575 """
576 runs = len(self.roundtimes)
577 if runs == 0:
578 return 0.0, 0.0
579 min_time = min(self.roundtimes)
580 total_time = reduce(operator.add, self.roundtimes, 0.0)
581 avg_time = total_time / float(runs)
582 max_time = max(self.roundtimes)
583 return (min_time, avg_time, max_time)
584
585 def print_header(self, title='Benchmark'):
586
587 print '-' * LINE
588 print '%s: %s' % (title, self.name)
589 print '-' * LINE
590 print
591 print ' Rounds: %s' % self.rounds
592 print ' Warp: %s' % self.warp
593 print ' Timer: %s' % self.timer
594 print
595 if self.machine_details:
596 print_machine_details(self.machine_details, indent=' ')
597 print
598
599 def print_benchmark(self, hidenoise=0, limitnames=None):
600
601 print ('Test '
602 ' minimum average operation overhead')
603 print '-' * LINE
604 tests = self.tests.items()
605 tests.sort()
606 total_min_time = 0.0
607 total_avg_time = 0.0
608 for name, test in tests:
609 if (limitnames is not None and
610 limitnames.search(name) is None):
611 continue
612 (min_time,
613 avg_time,
614 total_time,
615 op_avg,
616 min_overhead) = test.stat()
617 total_min_time = total_min_time + min_time
618 total_avg_time = total_avg_time + avg_time
619 print '%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
620 (name,
621 min_time * MILLI_SECONDS,
622 avg_time * MILLI_SECONDS,
623 op_avg * MICRO_SECONDS,
624 min_overhead *MILLI_SECONDS)
625 print '-' * LINE
626 print ('Totals: '
627 ' %6.0fms %6.0fms' %
628 (total_min_time * MILLI_SECONDS,
629 total_avg_time * MILLI_SECONDS,
630 ))
631 print
632
633 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
634
635 # Check benchmark versions
636 if compare_to.version != self.version:
637 print ('* Benchmark versions differ: '
638 'cannot compare this benchmark to "%s" !' %
639 compare_to.name)
640 print
641 self.print_benchmark(hidenoise=hidenoise,
642 limitnames=limitnames)
643 return
644
645 # Print header
646 compare_to.print_header('Comparing with')
647 print ('Test '
648 ' minimum run-time average run-time')
649 print (' '
650 ' this other diff this other diff')
651 print '-' * LINE
652
653 # Print test comparisons
654 tests = self.tests.items()
655 tests.sort()
656 total_min_time = other_total_min_time = 0.0
657 total_avg_time = other_total_avg_time = 0.0
658 benchmarks_compatible = self.compatible(compare_to)
659 tests_compatible = 1
660 for name, test in tests:
661 if (limitnames is not None and
662 limitnames.search(name) is None):
663 continue
664 (min_time,
665 avg_time,
666 total_time,
667 op_avg,
668 min_overhead) = test.stat()
669 total_min_time = total_min_time + min_time
670 total_avg_time = total_avg_time + avg_time
671 try:
672 other = compare_to.tests[name]
673 except KeyError:
674 other = None
675 if other is None:
676 # Other benchmark doesn't include the given test
677 min_diff, avg_diff = 'n/a', 'n/a'
678 other_min_time = 0.0
679 other_avg_time = 0.0
680 tests_compatible = 0
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000681 else:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000682 (other_min_time,
683 other_avg_time,
684 other_total_time,
685 other_op_avg,
686 other_min_overhead) = other.stat()
687 other_total_min_time = other_total_min_time + other_min_time
688 other_total_avg_time = other_total_avg_time + other_avg_time
689 if (benchmarks_compatible and
690 test.compatible(other)):
691 # Both benchmark and tests are comparible
692 min_diff = ((min_time * self.warp) /
693 (other_min_time * other.warp) - 1.0)
694 avg_diff = ((avg_time * self.warp) /
695 (other_avg_time * other.warp) - 1.0)
696 if hidenoise and abs(min_diff) < 10.0:
697 min_diff = ''
698 else:
699 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
700 if hidenoise and abs(avg_diff) < 10.0:
701 avg_diff = ''
702 else:
703 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
704 else:
705 # Benchmark or tests are not comparible
706 min_diff, avg_diff = 'n/a', 'n/a'
707 tests_compatible = 0
708 print '%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
709 (name,
710 min_time * MILLI_SECONDS,
711 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
712 min_diff,
713 avg_time * MILLI_SECONDS,
714 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
715 avg_diff)
716 print '-' * LINE
717
718 # Summarise test results
719 if not benchmarks_compatible or not tests_compatible:
720 min_diff, avg_diff = 'n/a', 'n/a'
721 else:
722 if other_total_min_time != 0.0:
723 min_diff = '%+5.1f%%' % (
724 ((total_min_time * self.warp) /
725 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
726 else:
727 min_diff = 'n/a'
728 if other_total_avg_time != 0.0:
729 avg_diff = '%+5.1f%%' % (
730 ((total_avg_time * self.warp) /
731 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
732 else:
733 avg_diff = 'n/a'
734 print ('Totals: '
735 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
736 (total_min_time * MILLI_SECONDS,
737 (other_total_min_time * compare_to.warp/self.warp
738 * MILLI_SECONDS),
739 min_diff,
740 total_avg_time * MILLI_SECONDS,
741 (other_total_avg_time * compare_to.warp/self.warp
742 * MILLI_SECONDS),
743 avg_diff
744 ))
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000745 print
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000746 print '(this=%s, other=%s)' % (self.name,
747 compare_to.name)
748 print
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000749
750class PyBenchCmdline(Application):
751
752 header = ("PYBENCH - a benchmark test suite for Python "
753 "interpreters/compilers.")
754
755 version = __version__
756
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000757 debug = _debug
758
759 options = [ArgumentOption('-n',
760 'number of rounds',
761 Setup.Number_of_rounds),
762 ArgumentOption('-f',
763 'save benchmark to file arg',
764 ''),
765 ArgumentOption('-c',
766 'compare benchmark with the one in file arg',
767 ''),
768 ArgumentOption('-s',
769 'show benchmark in file arg, then exit',
770 ''),
771 ArgumentOption('-w',
772 'set warp factor to arg',
773 Setup.Warp_factor),
774 ArgumentOption('-t',
775 'run only tests with names matching arg',
776 ''),
777 ArgumentOption('-C',
778 'set the number of calibration runs to arg',
779 CALIBRATION_RUNS),
780 SwitchOption('-d',
781 'hide noise in comparisons',
782 0),
783 SwitchOption('-v',
784 'verbose output (not recommended)',
785 0),
786 SwitchOption('--with-gc',
787 'enable garbage collection',
788 0),
789 SwitchOption('--with-syscheck',
790 'use default sys check interval',
791 0),
792 ArgumentOption('--timer',
793 'use given timer',
794 TIMER_PLATFORM_DEFAULT),
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000795 ]
796
797 about = """\
798The normal operation is to run the suite and display the
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000799results. Use -f to save them for later reuse or comparisons.
800
801Available timers:
802
803 time.time
804 time.clock
805 systimes.processtime
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000806
807Examples:
808
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000809python2.1 pybench.py -f p21.pybench
810python2.5 pybench.py -f p25.pybench
811python pybench.py -s p25.pybench -c p21.pybench
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000812"""
813 copyright = __copyright__
814
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000815 def main(self):
816
817 rounds = self.values['-n']
818 reportfile = self.values['-f']
819 show_bench = self.values['-s']
820 compare_to = self.values['-c']
821 hidenoise = self.values['-d']
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000822 warp = int(self.values['-w'])
823 withgc = self.values['--with-gc']
Steve Holden431a7632006-05-26 16:27:59 +0000824 limitnames = self.values['-t']
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000825 if limitnames:
826 if _debug:
827 print '* limiting test names to one with substring "%s"' % \
828 limitnames
829 limitnames = re.compile(limitnames, re.I)
830 else:
831 limitnames = None
Steve Holden431a7632006-05-26 16:27:59 +0000832 verbose = self.verbose
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000833 withsyscheck = self.values['--with-syscheck']
834 calibration_runs = self.values['-C']
835 timer = self.values['--timer']
Tim Peters19bfd422006-05-26 21:51:13 +0000836
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000837 print '-' * LINE
838 print 'PYBENCH %s' % __version__
839 print '-' * LINE
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000840 print '* using %s %s' % (
Jeffrey Yasskin3accbb02008-03-08 21:35:15 +0000841 getattr(platform, 'python_implementation', lambda:'Python')(),
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000842 string.join(string.split(sys.version), ' '))
Tim Petersf9cc5942006-04-21 16:34:54 +0000843
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000844 # Switch off garbage collection
845 if not withgc:
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000846 try:
847 import gc
848 except ImportError:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000849 print '* Python version doesn\'t support garbage collection'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000850 else:
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000851 try:
852 gc.disable()
853 except NotImplementedError:
854 print '* Python version doesn\'t support gc.disable'
855 else:
856 print '* disabled garbage collection'
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000857
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000858 # "Disable" sys check interval
859 if not withsyscheck:
860 # Too bad the check interval uses an int instead of a long...
861 value = 2147483647
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000862 try:
863 sys.setcheckinterval(value)
Marc-André Lemburgb1a8ef62007-01-13 23:15:33 +0000864 except (AttributeError, NotImplementedError):
Marc-André Lemburga50e6232007-01-13 22:59:36 +0000865 print '* Python version doesn\'t support sys.setcheckinterval'
866 else:
867 print '* system check interval set to maximum: %s' % value
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000868
869 if timer == TIMER_SYSTIMES_PROCESSTIME:
870 import systimes
871 print '* using timer: systimes.processtime (%s)' % \
872 systimes.SYSTIMES_IMPLEMENTATION
873 else:
874 print '* using timer: %s' % timer
Steve Holden431a7632006-05-26 16:27:59 +0000875
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000876 print
877
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000878 if compare_to:
879 try:
880 f = open(compare_to,'rb')
881 bench = pickle.load(f)
882 bench.name = compare_to
883 f.close()
884 compare_to = bench
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000885 except IOError, reason:
886 print '* Error opening/reading file %s: %s' % (
887 repr(compare_to),
888 reason)
Tim Petersf9cc5942006-04-21 16:34:54 +0000889 compare_to = None
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000890
891 if show_bench:
892 try:
893 f = open(show_bench,'rb')
894 bench = pickle.load(f)
895 bench.name = show_bench
896 f.close()
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000897 bench.print_header()
898 if compare_to:
899 bench.print_comparison(compare_to,
900 hidenoise=hidenoise,
901 limitnames=limitnames)
902 else:
903 bench.print_benchmark(hidenoise=hidenoise,
904 limitnames=limitnames)
Marc-André Lemburgf6fc4542006-08-29 10:34:12 +0000905 except IOError, reason:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000906 print '* Error opening/reading file %s: %s' % (
907 repr(show_bench),
908 reason)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000909 print
910 return
911
912 if reportfile:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000913 print 'Creating benchmark: %s (rounds=%i, warp=%i)' % \
914 (reportfile, rounds, warp)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000915 print
916
917 # Create benchmark object
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000918 bench = Benchmark(reportfile,
919 verbose=verbose,
920 timer=timer,
921 warp=warp,
922 calibration_runs=calibration_runs)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000923 bench.rounds = rounds
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000924 bench.load_tests(Setup, limitnames=limitnames)
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000925 try:
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000926 bench.calibrate()
927 bench.run()
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000928 except KeyboardInterrupt:
929 print
930 print '*** KeyboardInterrupt -- Aborting'
931 print
932 return
Marc-André Lemburg7d9743d2006-06-13 18:56:56 +0000933 bench.print_header()
934 if compare_to:
935 bench.print_comparison(compare_to,
936 hidenoise=hidenoise,
937 limitnames=limitnames)
938 else:
939 bench.print_benchmark(hidenoise=hidenoise,
940 limitnames=limitnames)
941
942 # Ring bell
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000943 sys.stderr.write('\007')
944
945 if reportfile:
946 try:
947 f = open(reportfile,'wb')
948 bench.name = reportfile
949 pickle.dump(bench,f)
950 f.close()
Marc-André Lemburgf6fc4542006-08-29 10:34:12 +0000951 except IOError, reason:
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000952 print '* Error opening/writing reportfile'
Marc-André Lemburgf6fc4542006-08-29 10:34:12 +0000953 except IOError, reason:
954 print '* Error opening/writing reportfile %s: %s' % (
955 reportfile,
956 reason)
957 print
Marc-André Lemburgc311f642006-04-19 15:27:33 +0000958
959if __name__ == '__main__':
960 PyBenchCmdline()