blob: 4c5ea26a95f7e7066ddae55b1013b24c5a088f61 [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
Guido van Rossum486364b2007-06-30 05:01:58 +000037import sys, time, operator, platform
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000038from CommandLine import *
39
40try:
41 import cPickle
42 pickle = cPickle
43except ImportError:
44 import pickle
45
Thomas Wouters0e3f5912006-08-11 14:57:12 +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
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000105 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000106 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000107 buildno, builddate = platform.python_build()
108 python = platform.python_version()
Antoine Pitrou251803b2008-07-22 18:03:03 +0000109 if sys.maxunicode == 65535:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000110 # UCS2 build (standard)
Georg Brandlbf82e372008-05-16 17:02:34 +0000111 unitype = 'UCS2'
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000112 else:
113 # UCS4 build (most recent Linux distros)
Georg Brandlbf82e372008-05-16 17:02:34 +0000114 unitype = 'UCS4'
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000115 bits, linkage = platform.architecture()
116 return {
117 'platform': platform.platform(),
118 'processor': platform.processor(),
119 'executable': sys.executable,
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000120 'implementation': getattr(platform, 'python_implementation',
121 lambda:'n/a')(),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000122 'python': platform.python_version(),
123 'compiler': platform.python_compiler(),
124 'buildno': buildno,
125 'builddate': builddate,
Georg Brandlbf82e372008-05-16 17:02:34 +0000126 'unicode': unitype,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127 'bits': bits,
128 }
129
130def print_machine_details(d, indent=''):
131
132 l = ['Machine Details:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000133 ' Platform ID: %s' % d.get('platform', 'n/a'),
134 ' Processor: %s' % d.get('processor', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000135 '',
136 'Python:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000137 ' Implementation: %s' % d.get('implementation', 'n/a'),
138 ' Executable: %s' % d.get('executable', 'n/a'),
139 ' Version: %s' % d.get('python', 'n/a'),
140 ' Compiler: %s' % d.get('compiler', 'n/a'),
141 ' Bits: %s' % d.get('bits', 'n/a'),
142 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
143 d.get('buildno', 'n/a')),
144 ' Unicode: %s' % d.get('unicode', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000145 ]
Guido van Rossum486364b2007-06-30 05:01:58 +0000146 joiner = '\n' + indent
147 print(indent + joiner.join(l) + '\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000148
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000149### Test baseclass
150
151class Test:
152
153 """ All test must have this class as baseclass. It provides
154 the necessary interface to the benchmark machinery.
155
156 The tests must set .rounds to a value high enough to let the
157 test run between 20-50 seconds. This is needed because
158 clock()-timing only gives rather inaccurate values (on Linux,
159 for example, it is accurate to a few hundreths of a
160 second). If you don't want to wait that long, use a warp
161 factor larger than 1.
162
163 It is also important to set the .operations variable to a
164 value representing the number of "virtual operations" done per
165 call of .run().
166
167 If you change a test in some way, don't forget to increase
Guido van Rossumd8faa362007-04-27 19:54:29 +0000168 its version number.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000169
170 """
171
172 ### Instance variables that each test should override
173
174 # Version number of the test as float (x.yy); this is important
175 # for comparisons of benchmark runs - tests with unequal version
176 # number will not get compared.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000177 version = 2.0
Thomas Wouters477c8d52006-05-27 19:21:47 +0000178
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000179 # The number of abstract operations done in each round of the
180 # test. An operation is the basic unit of what you want to
181 # measure. The benchmark will output the amount of run-time per
182 # operation. Note that in order to raise the measured timings
183 # significantly above noise level, it is often required to repeat
184 # sets of operations more than once per test round. The measured
185 # overhead per test round should be less than 1 second.
186 operations = 1
187
188 # Number of rounds to execute per test run. This should be
189 # adjusted to a figure that results in a test run-time of between
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000190 # 1-2 seconds.
191 rounds = 100000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000192
193 ### Internal variables
194
195 # Mark this class as implementing a test
196 is_a_test = 1
197
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000198 # Last timing: (real, run, overhead)
199 last_timing = (0.0, 0.0, 0.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000200
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000201 # Warp factor to use for this test
202 warp = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000203
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000204 # Number of calibration runs to use
205 calibration_runs = CALIBRATION_RUNS
206
207 # List of calibration timings
208 overhead_times = None
209
210 # List of test run timings
211 times = []
212
213 # Timer used for the benchmark
214 timer = TIMER_PLATFORM_DEFAULT
215
216 def __init__(self, warp=None, calibration_runs=None, timer=None):
217
218 # Set parameters
219 if warp is not None:
220 self.rounds = int(self.rounds / warp)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000221 if self.rounds == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000222 raise ValueError('warp factor set too high')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000223 self.warp = warp
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000224 if calibration_runs is not None:
225 if (not ALLOW_SKIPPING_CALIBRATION and
226 calibration_runs < 1):
227 raise ValueError('at least one calibration run is required')
228 self.calibration_runs = calibration_runs
229 if timer is not None:
230 timer = timer
231
232 # Init variables
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000233 self.times = []
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000234 self.overhead_times = []
235
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000236 # We want these to be in the instance dict, so that pickle
237 # saves them
238 self.version = self.version
239 self.operations = self.operations
240 self.rounds = self.rounds
241
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000242 def get_timer(self):
243
244 """ Return the timer function to use for the test.
245
246 """
247 return get_timer(self.timer)
248
249 def compatible(self, other):
250
251 """ Return 1/0 depending on whether the test is compatible
252 with the other Test instance or not.
253
254 """
255 if self.version != other.version:
256 return 0
257 if self.rounds != other.rounds:
258 return 0
259 return 1
260
261 def calibrate_test(self):
262
263 if self.calibration_runs == 0:
264 self.overhead_times = [0.0]
265 return
266
267 calibrate = self.calibrate
268 timer = self.get_timer()
269 calibration_loops = range(CALIBRATION_LOOPS)
270
271 # Time the calibration loop overhead
272 prep_times = []
273 for i in range(self.calibration_runs):
274 t = timer()
275 for i in calibration_loops:
276 pass
277 t = timer() - t
278 prep_times.append(t)
279 min_prep_time = min(prep_times)
280 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000281 print()
282 print('Calib. prep time = %.6fms' % (
283 min_prep_time * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000284
285 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
286 # .calibrate() method calls each)
287 for i in range(self.calibration_runs):
288 t = timer()
289 for i in calibration_loops:
290 calibrate()
291 t = timer() - t
292 self.overhead_times.append(t / CALIBRATION_LOOPS
293 - min_prep_time)
294
295 # Check the measured times
296 min_overhead = min(self.overhead_times)
297 max_overhead = max(self.overhead_times)
298 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000299 print('Calib. overhead time = %.6fms' % (
300 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000301 if min_overhead < 0.0:
302 raise ValueError('calibration setup did not work')
303 if max_overhead - min_overhead > 0.1:
304 raise ValueError(
305 'overhead calibration timing range too inaccurate: '
306 '%r - %r' % (min_overhead, max_overhead))
307
308 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000309
310 """ Run the test in two phases: first calibrate, then
311 do the actual test. Be careful to keep the calibration
312 timing low w/r to the test timing.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000313
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000314 """
315 test = self.test
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000316 timer = self.get_timer()
317
318 # Get calibration
319 min_overhead = min(self.overhead_times)
320
321 # Test run
322 t = timer()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000323 test()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000324 t = timer() - t
325 if t < MIN_TEST_RUNTIME:
326 raise ValueError('warp factor too high: '
327 'test times are < 10ms')
328 eff_time = t - min_overhead
329 if eff_time < 0:
330 raise ValueError('wrong calibration')
331 self.last_timing = (eff_time, t, min_overhead)
332 self.times.append(eff_time)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000333
334 def calibrate(self):
335
Thomas Wouters477c8d52006-05-27 19:21:47 +0000336 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000337
Thomas Wouters477c8d52006-05-27 19:21:47 +0000338 This method should execute everything that is needed to
339 setup and run the test - except for the actual operations
340 that you intend to measure. pybench uses this method to
341 measure the test implementation overhead.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000342
343 """
344 return
345
346 def test(self):
347
Thomas Wouters477c8d52006-05-27 19:21:47 +0000348 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000349
Thomas Wouters477c8d52006-05-27 19:21:47 +0000350 The test needs to run self.rounds executing
351 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000352
353 """
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000354 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000355
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000356 def stat(self):
357
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000358 """ Return test run statistics as tuple:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000359
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000360 (minimum run time,
361 average run time,
362 total run time,
363 average time per operation,
364 minimum overhead time)
365
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000366 """
367 runs = len(self.times)
368 if runs == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000369 return 0.0, 0.0, 0.0, 0.0
370 min_time = min(self.times)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000371 total_time = sum(self.times)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000372 avg_time = total_time / float(runs)
373 operation_avg = total_time / float(runs
374 * self.rounds
375 * self.operations)
376 if self.overhead_times:
377 min_overhead = min(self.overhead_times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000378 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000379 min_overhead = self.last_timing[2]
380 return min_time, avg_time, total_time, operation_avg, min_overhead
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000381
382### Load Setup
383
384# This has to be done after the definition of the Test class, since
385# the Setup module will import subclasses using this class.
386
387import Setup
388
389### Benchmark base class
390
391class Benchmark:
392
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000393 # Name of the benchmark
394 name = ''
395
396 # Number of benchmark rounds to run
397 rounds = 1
398
399 # Warp factor use to run the tests
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000400 warp = 1 # Warp factor
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000401
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000402 # Average benchmark round time
403 roundtime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000404
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000405 # Benchmark version number as float x.yy
406 version = 2.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000407
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000408 # Produce verbose output ?
409 verbose = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000410
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000411 # Dictionary with the machine details
412 machine_details = None
413
414 # Timer used for the benchmark
415 timer = TIMER_PLATFORM_DEFAULT
416
417 def __init__(self, name, verbose=None, timer=None, warp=None,
418 calibration_runs=None):
419
420 if name:
421 self.name = name
Thomas Wouters477c8d52006-05-27 19:21:47 +0000422 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000423 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
424 (time.localtime(time.time())[:6])
425 if verbose is not None:
426 self.verbose = verbose
427 if timer is not None:
428 self.timer = timer
429 if warp is not None:
430 self.warp = warp
431 if calibration_runs is not None:
432 self.calibration_runs = calibration_runs
433
434 # Init vars
435 self.tests = {}
436 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000437 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000438 self.machine_details = get_machine_details()
439
440 # Make .version an instance attribute to have it saved in the
441 # Benchmark pickle
442 self.version = self.version
443
444 def get_timer(self):
445
446 """ Return the timer function to use for the test.
447
448 """
449 return get_timer(self.timer)
450
451 def compatible(self, other):
452
453 """ Return 1/0 depending on whether the benchmark is
454 compatible with the other Benchmark instance or not.
455
456 """
457 if self.version != other.version:
458 return 0
459 if (self.machine_details == other.machine_details and
460 self.timer != other.timer):
461 return 0
462 if (self.calibration_runs == 0 and
463 other.calibration_runs != 0):
464 return 0
465 if (self.calibration_runs != 0 and
466 other.calibration_runs == 0):
467 return 0
468 return 1
469
470 def load_tests(self, setupmod, limitnames=None):
471
472 # Add tests
473 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000474 print('Searching for tests ...')
475 print('--------------------------------------')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000476 for testclass in setupmod.__dict__.values():
477 if not hasattr(testclass, 'is_a_test'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000478 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000479 name = testclass.__name__
Thomas Wouters477c8d52006-05-27 19:21:47 +0000480 if name == 'Test':
481 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000482 if (limitnames is not None and
483 limitnames.search(name) is None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000485 self.tests[name] = testclass(
486 warp=self.warp,
487 calibration_runs=self.calibration_runs,
488 timer=self.timer)
Guido van Rossum486364b2007-06-30 05:01:58 +0000489 l = sorted(self.tests)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000490 if self.verbose:
491 for name in l:
Guido van Rossum486364b2007-06-30 05:01:58 +0000492 print(' %s' % name)
493 print('--------------------------------------')
494 print(' %i tests found' % len(l))
495 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000496
497 def calibrate(self):
498
Guido van Rossum486364b2007-06-30 05:01:58 +0000499 print('Calibrating tests. Please wait...', end=' ')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000500 sys.stdout.flush()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000501 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000502 print()
503 print()
504 print('Test min max')
505 print('-' * LINE)
506 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000507 for i in range(len(tests)):
508 name, test = tests[i]
509 test.calibrate_test()
510 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000511 print('%30s: %6.3fms %6.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000512 (name,
513 min(test.overhead_times) * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000514 max(test.overhead_times) * MILLI_SECONDS))
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000515 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000516 print()
517 print('Done with the calibration.')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000518 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000519 print('done.')
520 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000521
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000522 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000523
Guido van Rossum486364b2007-06-30 05:01:58 +0000524 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000525 timer = self.get_timer()
Guido van Rossum486364b2007-06-30 05:01:58 +0000526 print('Running %i round(s) of the suite at warp factor %i:' % \
527 (self.rounds, self.warp))
528 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000529 self.roundtimes = []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000530 for i in range(self.rounds):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000531 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000532 print(' Round %-25i effective absolute overhead' % (i+1))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000533 total_eff_time = 0.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000534 for j in range(len(tests)):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000535 name, test = tests[j]
536 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000537 print('%30s:' % name, end=' ')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000538 test.run()
539 (eff_time, abs_time, min_overhead) = test.last_timing
540 total_eff_time = total_eff_time + eff_time
541 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000542 print(' %5.0fms %5.0fms %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000543 (eff_time * MILLI_SECONDS,
544 abs_time * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000545 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000546 self.roundtimes.append(total_eff_time)
547 if self.verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +0000548 print(' '
549 ' ------------------------------')
550 print(' '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000551 ' Totals: %6.0fms' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000552 (total_eff_time * MILLI_SECONDS))
Guido van Rossum486364b2007-06-30 05:01:58 +0000553 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000554 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000555 print('* Round %i done in %.3f seconds.' % (i+1,
556 total_eff_time))
557 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000558
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000559 def stat(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000560
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000561 """ Return benchmark run statistics as tuple:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000562
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000563 (minimum round time,
564 average round time,
565 maximum round time)
566
567 XXX Currently not used, since the benchmark does test
568 statistics across all rounds.
569
570 """
571 runs = len(self.roundtimes)
572 if runs == 0:
573 return 0.0, 0.0
574 min_time = min(self.roundtimes)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000575 total_time = sum(self.roundtimes)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000576 avg_time = total_time / float(runs)
577 max_time = max(self.roundtimes)
578 return (min_time, avg_time, max_time)
579
580 def print_header(self, title='Benchmark'):
581
Guido van Rossum486364b2007-06-30 05:01:58 +0000582 print('-' * LINE)
583 print('%s: %s' % (title, self.name))
584 print('-' * LINE)
585 print()
586 print(' Rounds: %s' % self.rounds)
587 print(' Warp: %s' % self.warp)
588 print(' Timer: %s' % self.timer)
589 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000590 if self.machine_details:
591 print_machine_details(self.machine_details, indent=' ')
Guido van Rossum486364b2007-06-30 05:01:58 +0000592 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000593
594 def print_benchmark(self, hidenoise=0, limitnames=None):
595
Collin Winter6afaeb72007-08-03 17:06:41 +0000596 print('Test '
597 ' minimum average operation overhead')
Guido van Rossum486364b2007-06-30 05:01:58 +0000598 print('-' * LINE)
599 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000600 total_min_time = 0.0
601 total_avg_time = 0.0
602 for name, test in tests:
603 if (limitnames is not None and
604 limitnames.search(name) is None):
605 continue
606 (min_time,
607 avg_time,
608 total_time,
609 op_avg,
610 min_overhead) = test.stat()
611 total_min_time = total_min_time + min_time
612 total_avg_time = total_avg_time + avg_time
Guido van Rossum486364b2007-06-30 05:01:58 +0000613 print('%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000614 (name,
615 min_time * MILLI_SECONDS,
616 avg_time * MILLI_SECONDS,
617 op_avg * MICRO_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000618 min_overhead *MILLI_SECONDS))
619 print('-' * LINE)
Collin Winter6afaeb72007-08-03 17:06:41 +0000620 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000621 ' %6.0fms %6.0fms' %
622 (total_min_time * MILLI_SECONDS,
623 total_avg_time * MILLI_SECONDS,
Collin Winter6afaeb72007-08-03 17:06:41 +0000624 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000625 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000626
627 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
628
629 # Check benchmark versions
630 if compare_to.version != self.version:
Collin Winter6afaeb72007-08-03 17:06:41 +0000631 print('* Benchmark versions differ: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000632 'cannot compare this benchmark to "%s" !' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000633 compare_to.name)
Guido van Rossum486364b2007-06-30 05:01:58 +0000634 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000635 self.print_benchmark(hidenoise=hidenoise,
636 limitnames=limitnames)
637 return
638
639 # Print header
640 compare_to.print_header('Comparing with')
Collin Winter6afaeb72007-08-03 17:06:41 +0000641 print('Test '
642 ' minimum run-time average run-time')
643 print(' '
644 ' this other diff this other diff')
Guido van Rossum486364b2007-06-30 05:01:58 +0000645 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000646
647 # Print test comparisons
Guido van Rossum486364b2007-06-30 05:01:58 +0000648 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000649 total_min_time = other_total_min_time = 0.0
650 total_avg_time = other_total_avg_time = 0.0
651 benchmarks_compatible = self.compatible(compare_to)
652 tests_compatible = 1
653 for name, test in tests:
654 if (limitnames is not None and
655 limitnames.search(name) is None):
656 continue
657 (min_time,
658 avg_time,
659 total_time,
660 op_avg,
661 min_overhead) = test.stat()
662 total_min_time = total_min_time + min_time
663 total_avg_time = total_avg_time + avg_time
664 try:
665 other = compare_to.tests[name]
666 except KeyError:
667 other = None
668 if other is None:
669 # Other benchmark doesn't include the given test
670 min_diff, avg_diff = 'n/a', 'n/a'
671 other_min_time = 0.0
672 other_avg_time = 0.0
673 tests_compatible = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000674 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000675 (other_min_time,
676 other_avg_time,
677 other_total_time,
678 other_op_avg,
679 other_min_overhead) = other.stat()
680 other_total_min_time = other_total_min_time + other_min_time
681 other_total_avg_time = other_total_avg_time + other_avg_time
682 if (benchmarks_compatible and
683 test.compatible(other)):
684 # Both benchmark and tests are comparible
685 min_diff = ((min_time * self.warp) /
686 (other_min_time * other.warp) - 1.0)
687 avg_diff = ((avg_time * self.warp) /
688 (other_avg_time * other.warp) - 1.0)
689 if hidenoise and abs(min_diff) < 10.0:
690 min_diff = ''
691 else:
692 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
693 if hidenoise and abs(avg_diff) < 10.0:
694 avg_diff = ''
695 else:
696 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
697 else:
698 # Benchmark or tests are not comparible
699 min_diff, avg_diff = 'n/a', 'n/a'
700 tests_compatible = 0
Guido van Rossum486364b2007-06-30 05:01:58 +0000701 print('%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000702 (name,
703 min_time * MILLI_SECONDS,
704 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
705 min_diff,
706 avg_time * MILLI_SECONDS,
707 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
Guido van Rossum486364b2007-06-30 05:01:58 +0000708 avg_diff))
709 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000710
711 # Summarise test results
712 if not benchmarks_compatible or not tests_compatible:
713 min_diff, avg_diff = 'n/a', 'n/a'
714 else:
715 if other_total_min_time != 0.0:
716 min_diff = '%+5.1f%%' % (
717 ((total_min_time * self.warp) /
718 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
719 else:
720 min_diff = 'n/a'
721 if other_total_avg_time != 0.0:
722 avg_diff = '%+5.1f%%' % (
723 ((total_avg_time * self.warp) /
724 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
725 else:
726 avg_diff = 'n/a'
Collin Winter6afaeb72007-08-03 17:06:41 +0000727 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000728 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
729 (total_min_time * MILLI_SECONDS,
730 (other_total_min_time * compare_to.warp/self.warp
731 * MILLI_SECONDS),
732 min_diff,
733 total_avg_time * MILLI_SECONDS,
734 (other_total_avg_time * compare_to.warp/self.warp
735 * MILLI_SECONDS),
736 avg_diff
Collin Winter6afaeb72007-08-03 17:06:41 +0000737 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000738 print()
739 print('(this=%s, other=%s)' % (self.name,
740 compare_to.name))
741 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000742
743class PyBenchCmdline(Application):
744
745 header = ("PYBENCH - a benchmark test suite for Python "
746 "interpreters/compilers.")
747
748 version = __version__
749
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000750 debug = _debug
751
752 options = [ArgumentOption('-n',
753 'number of rounds',
754 Setup.Number_of_rounds),
755 ArgumentOption('-f',
756 'save benchmark to file arg',
757 ''),
758 ArgumentOption('-c',
759 'compare benchmark with the one in file arg',
760 ''),
761 ArgumentOption('-s',
762 'show benchmark in file arg, then exit',
763 ''),
764 ArgumentOption('-w',
765 'set warp factor to arg',
766 Setup.Warp_factor),
767 ArgumentOption('-t',
768 'run only tests with names matching arg',
769 ''),
770 ArgumentOption('-C',
771 'set the number of calibration runs to arg',
772 CALIBRATION_RUNS),
773 SwitchOption('-d',
774 'hide noise in comparisons',
775 0),
776 SwitchOption('-v',
777 'verbose output (not recommended)',
778 0),
779 SwitchOption('--with-gc',
780 'enable garbage collection',
781 0),
782 SwitchOption('--with-syscheck',
783 'use default sys check interval',
784 0),
785 ArgumentOption('--timer',
786 'use given timer',
787 TIMER_PLATFORM_DEFAULT),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000788 ]
789
790 about = """\
791The normal operation is to run the suite and display the
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000792results. Use -f to save them for later reuse or comparisons.
793
794Available timers:
795
796 time.time
797 time.clock
798 systimes.processtime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000799
800Examples:
801
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000802python2.1 pybench.py -f p21.pybench
803python2.5 pybench.py -f p25.pybench
804python pybench.py -s p25.pybench -c p21.pybench
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000805"""
806 copyright = __copyright__
807
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000808 def main(self):
809
810 rounds = self.values['-n']
811 reportfile = self.values['-f']
812 show_bench = self.values['-s']
813 compare_to = self.values['-c']
814 hidenoise = self.values['-d']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000815 warp = int(self.values['-w'])
816 withgc = self.values['--with-gc']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000817 limitnames = self.values['-t']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000818 if limitnames:
819 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000820 print('* limiting test names to one with substring "%s"' % \
821 limitnames)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000822 limitnames = re.compile(limitnames, re.I)
823 else:
824 limitnames = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000825 verbose = self.verbose
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000826 withsyscheck = self.values['--with-syscheck']
827 calibration_runs = self.values['-C']
828 timer = self.values['--timer']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000829
Guido van Rossum486364b2007-06-30 05:01:58 +0000830 print('-' * LINE)
831 print('PYBENCH %s' % __version__)
832 print('-' * LINE)
833 print('* using %s %s' % (
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000834 getattr(platform, 'python_implementation', lambda:'Python')(),
Guido van Rossum486364b2007-06-30 05:01:58 +0000835 ' '.join(sys.version.split())))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000836
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000837 # Switch off garbage collection
838 if not withgc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000839 try:
840 import gc
841 except ImportError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000842 print('* Python version doesn\'t support garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000843 else:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000844 try:
845 gc.disable()
846 except NotImplementedError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000847 print('* Python version doesn\'t support gc.disable')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000848 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000849 print('* disabled garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000850
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000851 # "Disable" sys check interval
852 if not withsyscheck:
853 # Too bad the check interval uses an int instead of a long...
854 value = 2147483647
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000855 try:
856 sys.setcheckinterval(value)
857 except (AttributeError, NotImplementedError):
Guido van Rossum486364b2007-06-30 05:01:58 +0000858 print('* Python version doesn\'t support sys.setcheckinterval')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000859 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000860 print('* system check interval set to maximum: %s' % value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000861
862 if timer == TIMER_SYSTIMES_PROCESSTIME:
863 import systimes
Guido van Rossum486364b2007-06-30 05:01:58 +0000864 print('* using timer: systimes.processtime (%s)' % \
865 systimes.SYSTIMES_IMPLEMENTATION)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000866 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000867 print('* using timer: %s' % timer)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000868
Guido van Rossum486364b2007-06-30 05:01:58 +0000869 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000870
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000871 if compare_to:
872 try:
873 f = open(compare_to,'rb')
874 bench = pickle.load(f)
875 bench.name = compare_to
876 f.close()
877 compare_to = bench
Guido van Rossumb940e112007-01-10 16:19:56 +0000878 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000879 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000880 repr(compare_to),
Guido van Rossum486364b2007-06-30 05:01:58 +0000881 reason))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000882 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000883
884 if show_bench:
885 try:
886 f = open(show_bench,'rb')
887 bench = pickle.load(f)
888 bench.name = show_bench
889 f.close()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000890 bench.print_header()
891 if compare_to:
892 bench.print_comparison(compare_to,
893 hidenoise=hidenoise,
894 limitnames=limitnames)
895 else:
896 bench.print_benchmark(hidenoise=hidenoise,
897 limitnames=limitnames)
Guido van Rossumb940e112007-01-10 16:19:56 +0000898 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000899 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000900 repr(show_bench),
Guido van Rossum486364b2007-06-30 05:01:58 +0000901 reason))
902 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000903 return
904
905 if reportfile:
Guido van Rossum486364b2007-06-30 05:01:58 +0000906 print('Creating benchmark: %s (rounds=%i, warp=%i)' % \
907 (reportfile, rounds, warp))
908 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000909
910 # Create benchmark object
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000911 bench = Benchmark(reportfile,
912 verbose=verbose,
913 timer=timer,
914 warp=warp,
915 calibration_runs=calibration_runs)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000916 bench.rounds = rounds
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000917 bench.load_tests(Setup, limitnames=limitnames)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000918 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000919 bench.calibrate()
920 bench.run()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000921 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000922 print()
923 print('*** KeyboardInterrupt -- Aborting')
924 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000925 return
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000926 bench.print_header()
927 if compare_to:
928 bench.print_comparison(compare_to,
929 hidenoise=hidenoise,
930 limitnames=limitnames)
931 else:
932 bench.print_benchmark(hidenoise=hidenoise,
933 limitnames=limitnames)
934
935 # Ring bell
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000936 sys.stderr.write('\007')
937
938 if reportfile:
939 try:
940 f = open(reportfile,'wb')
941 bench.name = reportfile
942 pickle.dump(bench,f)
943 f.close()
Guido van Rossumb940e112007-01-10 16:19:56 +0000944 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000945 print('* Error opening/writing reportfile')
Guido van Rossumb940e112007-01-10 16:19:56 +0000946 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000947 print('* Error opening/writing reportfile %s: %s' % (
Thomas Wouters89f507f2006-12-13 04:49:30 +0000948 reportfile,
Guido van Rossum486364b2007-06-30 05:01:58 +0000949 reason))
950 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000951
952if __name__ == '__main__':
953 PyBenchCmdline()