blob: 781b4f824dea87f30117d5e38ad89795189447af [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()
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000109 try:
110 unichr(100000)
111 except ValueError:
112 # UCS2 build (standard)
113 unicode = 'UCS2'
114 except NameError:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000115 unicode = None
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000116 else:
117 # UCS4 build (most recent Linux distros)
118 unicode = 'UCS4'
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000119 bits, linkage = platform.architecture()
120 return {
121 'platform': platform.platform(),
122 'processor': platform.processor(),
123 'executable': sys.executable,
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000124 'implementation': getattr(platform, 'python_implementation',
125 lambda:'n/a')(),
Thomas Wouters0e3f5912006-08-11 14:57:12 +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:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000137 ' Platform ID: %s' % d.get('platform', 'n/a'),
138 ' Processor: %s' % d.get('processor', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000139 '',
140 'Python:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +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'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000149 ]
Guido van Rossum486364b2007-06-30 05:01:58 +0000150 joiner = '\n' + indent
151 print(indent + joiner.join(l) + '\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000152
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000153### Test baseclass
154
155class Test:
156
157 """ All test must have this class as baseclass. It provides
158 the necessary interface to the benchmark machinery.
159
160 The tests must set .rounds to a value high enough to let the
161 test run between 20-50 seconds. This is needed because
162 clock()-timing only gives rather inaccurate values (on Linux,
163 for example, it is accurate to a few hundreths of a
164 second). If you don't want to wait that long, use a warp
165 factor larger than 1.
166
167 It is also important to set the .operations variable to a
168 value representing the number of "virtual operations" done per
169 call of .run().
170
171 If you change a test in some way, don't forget to increase
Guido van Rossumd8faa362007-04-27 19:54:29 +0000172 its version number.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000173
174 """
175
176 ### Instance variables that each test should override
177
178 # Version number of the test as float (x.yy); this is important
179 # for comparisons of benchmark runs - tests with unequal version
180 # number will not get compared.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000181 version = 2.0
Thomas Wouters477c8d52006-05-27 19:21:47 +0000182
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000183 # The number of abstract operations done in each round of the
184 # test. An operation is the basic unit of what you want to
185 # measure. The benchmark will output the amount of run-time per
186 # operation. Note that in order to raise the measured timings
187 # significantly above noise level, it is often required to repeat
188 # sets of operations more than once per test round. The measured
189 # overhead per test round should be less than 1 second.
190 operations = 1
191
192 # Number of rounds to execute per test run. This should be
193 # adjusted to a figure that results in a test run-time of between
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000194 # 1-2 seconds.
195 rounds = 100000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000196
197 ### Internal variables
198
199 # Mark this class as implementing a test
200 is_a_test = 1
201
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000202 # Last timing: (real, run, overhead)
203 last_timing = (0.0, 0.0, 0.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000204
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000205 # Warp factor to use for this test
206 warp = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000207
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000208 # Number of calibration runs to use
209 calibration_runs = CALIBRATION_RUNS
210
211 # List of calibration timings
212 overhead_times = None
213
214 # List of test run timings
215 times = []
216
217 # Timer used for the benchmark
218 timer = TIMER_PLATFORM_DEFAULT
219
220 def __init__(self, warp=None, calibration_runs=None, timer=None):
221
222 # Set parameters
223 if warp is not None:
224 self.rounds = int(self.rounds / warp)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000225 if self.rounds == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000226 raise ValueError('warp factor set too high')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000227 self.warp = warp
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000228 if calibration_runs is not None:
229 if (not ALLOW_SKIPPING_CALIBRATION and
230 calibration_runs < 1):
231 raise ValueError('at least one calibration run is required')
232 self.calibration_runs = calibration_runs
233 if timer is not None:
234 timer = timer
235
236 # Init variables
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000237 self.times = []
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000238 self.overhead_times = []
239
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000240 # We want these to be in the instance dict, so that pickle
241 # saves them
242 self.version = self.version
243 self.operations = self.operations
244 self.rounds = self.rounds
245
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000246 def get_timer(self):
247
248 """ Return the timer function to use for the test.
249
250 """
251 return get_timer(self.timer)
252
253 def compatible(self, other):
254
255 """ Return 1/0 depending on whether the test is compatible
256 with the other Test instance or not.
257
258 """
259 if self.version != other.version:
260 return 0
261 if self.rounds != other.rounds:
262 return 0
263 return 1
264
265 def calibrate_test(self):
266
267 if self.calibration_runs == 0:
268 self.overhead_times = [0.0]
269 return
270
271 calibrate = self.calibrate
272 timer = self.get_timer()
273 calibration_loops = range(CALIBRATION_LOOPS)
274
275 # Time the calibration loop overhead
276 prep_times = []
277 for i in range(self.calibration_runs):
278 t = timer()
279 for i in calibration_loops:
280 pass
281 t = timer() - t
282 prep_times.append(t)
283 min_prep_time = min(prep_times)
284 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000285 print()
286 print('Calib. prep time = %.6fms' % (
287 min_prep_time * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000288
289 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
290 # .calibrate() method calls each)
291 for i in range(self.calibration_runs):
292 t = timer()
293 for i in calibration_loops:
294 calibrate()
295 t = timer() - t
296 self.overhead_times.append(t / CALIBRATION_LOOPS
297 - min_prep_time)
298
299 # Check the measured times
300 min_overhead = min(self.overhead_times)
301 max_overhead = max(self.overhead_times)
302 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000303 print('Calib. overhead time = %.6fms' % (
304 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000305 if min_overhead < 0.0:
306 raise ValueError('calibration setup did not work')
307 if max_overhead - min_overhead > 0.1:
308 raise ValueError(
309 'overhead calibration timing range too inaccurate: '
310 '%r - %r' % (min_overhead, max_overhead))
311
312 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000313
314 """ Run the test in two phases: first calibrate, then
315 do the actual test. Be careful to keep the calibration
316 timing low w/r to the test timing.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000317
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000318 """
319 test = self.test
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000320 timer = self.get_timer()
321
322 # Get calibration
323 min_overhead = min(self.overhead_times)
324
325 # Test run
326 t = timer()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000327 test()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000328 t = timer() - t
329 if t < MIN_TEST_RUNTIME:
330 raise ValueError('warp factor too high: '
331 'test times are < 10ms')
332 eff_time = t - min_overhead
333 if eff_time < 0:
334 raise ValueError('wrong calibration')
335 self.last_timing = (eff_time, t, min_overhead)
336 self.times.append(eff_time)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000337
338 def calibrate(self):
339
Thomas Wouters477c8d52006-05-27 19:21:47 +0000340 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000341
Thomas Wouters477c8d52006-05-27 19:21:47 +0000342 This method should execute everything that is needed to
343 setup and run the test - except for the actual operations
344 that you intend to measure. pybench uses this method to
345 measure the test implementation overhead.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000346
347 """
348 return
349
350 def test(self):
351
Thomas Wouters477c8d52006-05-27 19:21:47 +0000352 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000353
Thomas Wouters477c8d52006-05-27 19:21:47 +0000354 The test needs to run self.rounds executing
355 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000356
357 """
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000358 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000359
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000360 def stat(self):
361
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000362 """ Return test run statistics as tuple:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000363
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000364 (minimum run time,
365 average run time,
366 total run time,
367 average time per operation,
368 minimum overhead time)
369
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000370 """
371 runs = len(self.times)
372 if runs == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000373 return 0.0, 0.0, 0.0, 0.0
374 min_time = min(self.times)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000375 total_time = sum(self.times)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000376 avg_time = total_time / float(runs)
377 operation_avg = total_time / float(runs
378 * self.rounds
379 * self.operations)
380 if self.overhead_times:
381 min_overhead = min(self.overhead_times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000382 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000383 min_overhead = self.last_timing[2]
384 return min_time, avg_time, total_time, operation_avg, min_overhead
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000385
386### Load Setup
387
388# This has to be done after the definition of the Test class, since
389# the Setup module will import subclasses using this class.
390
391import Setup
392
393### Benchmark base class
394
395class Benchmark:
396
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000397 # Name of the benchmark
398 name = ''
399
400 # Number of benchmark rounds to run
401 rounds = 1
402
403 # Warp factor use to run the tests
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000404 warp = 1 # Warp factor
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000405
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000406 # Average benchmark round time
407 roundtime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000408
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000409 # Benchmark version number as float x.yy
410 version = 2.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000411
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412 # Produce verbose output ?
413 verbose = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000414
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000415 # Dictionary with the machine details
416 machine_details = None
417
418 # Timer used for the benchmark
419 timer = TIMER_PLATFORM_DEFAULT
420
421 def __init__(self, name, verbose=None, timer=None, warp=None,
422 calibration_runs=None):
423
424 if name:
425 self.name = name
Thomas Wouters477c8d52006-05-27 19:21:47 +0000426 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000427 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
428 (time.localtime(time.time())[:6])
429 if verbose is not None:
430 self.verbose = verbose
431 if timer is not None:
432 self.timer = timer
433 if warp is not None:
434 self.warp = warp
435 if calibration_runs is not None:
436 self.calibration_runs = calibration_runs
437
438 # Init vars
439 self.tests = {}
440 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000441 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000442 self.machine_details = get_machine_details()
443
444 # Make .version an instance attribute to have it saved in the
445 # Benchmark pickle
446 self.version = self.version
447
448 def get_timer(self):
449
450 """ Return the timer function to use for the test.
451
452 """
453 return get_timer(self.timer)
454
455 def compatible(self, other):
456
457 """ Return 1/0 depending on whether the benchmark is
458 compatible with the other Benchmark instance or not.
459
460 """
461 if self.version != other.version:
462 return 0
463 if (self.machine_details == other.machine_details and
464 self.timer != other.timer):
465 return 0
466 if (self.calibration_runs == 0 and
467 other.calibration_runs != 0):
468 return 0
469 if (self.calibration_runs != 0 and
470 other.calibration_runs == 0):
471 return 0
472 return 1
473
474 def load_tests(self, setupmod, limitnames=None):
475
476 # Add tests
477 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000478 print('Searching for tests ...')
479 print('--------------------------------------')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000480 for testclass in setupmod.__dict__.values():
481 if not hasattr(testclass, 'is_a_test'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000483 name = testclass.__name__
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484 if name == 'Test':
485 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000486 if (limitnames is not None and
487 limitnames.search(name) is None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000488 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000489 self.tests[name] = testclass(
490 warp=self.warp,
491 calibration_runs=self.calibration_runs,
492 timer=self.timer)
Guido van Rossum486364b2007-06-30 05:01:58 +0000493 l = sorted(self.tests)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000494 if self.verbose:
495 for name in l:
Guido van Rossum486364b2007-06-30 05:01:58 +0000496 print(' %s' % name)
497 print('--------------------------------------')
498 print(' %i tests found' % len(l))
499 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000500
501 def calibrate(self):
502
Guido van Rossum486364b2007-06-30 05:01:58 +0000503 print('Calibrating tests. Please wait...', end=' ')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000504 sys.stdout.flush()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000505 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000506 print()
507 print()
508 print('Test min max')
509 print('-' * LINE)
510 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000511 for i in range(len(tests)):
512 name, test = tests[i]
513 test.calibrate_test()
514 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000515 print('%30s: %6.3fms %6.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000516 (name,
517 min(test.overhead_times) * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000518 max(test.overhead_times) * MILLI_SECONDS))
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000519 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000520 print()
521 print('Done with the calibration.')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000522 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000523 print('done.')
524 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000525
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000526 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000527
Guido van Rossum486364b2007-06-30 05:01:58 +0000528 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000529 timer = self.get_timer()
Guido van Rossum486364b2007-06-30 05:01:58 +0000530 print('Running %i round(s) of the suite at warp factor %i:' % \
531 (self.rounds, self.warp))
532 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000533 self.roundtimes = []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000534 for i in range(self.rounds):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000535 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000536 print(' Round %-25i effective absolute overhead' % (i+1))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000537 total_eff_time = 0.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000538 for j in range(len(tests)):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000539 name, test = tests[j]
540 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000541 print('%30s:' % name, end=' ')
Thomas Wouters0e3f5912006-08-11 14:57:12 +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:
Guido van Rossum486364b2007-06-30 05:01:58 +0000546 print(' %5.0fms %5.0fms %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 (eff_time * MILLI_SECONDS,
548 abs_time * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000549 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000550 self.roundtimes.append(total_eff_time)
551 if self.verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +0000552 print(' '
553 ' ------------------------------')
554 print(' '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000555 ' Totals: %6.0fms' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000556 (total_eff_time * MILLI_SECONDS))
Guido van Rossum486364b2007-06-30 05:01:58 +0000557 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000558 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000559 print('* Round %i done in %.3f seconds.' % (i+1,
560 total_eff_time))
561 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000562
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000563 def stat(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000564
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000565 """ Return benchmark run statistics as tuple:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000566
Thomas Wouters0e3f5912006-08-11 14:57:12 +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)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000579 total_time = sum(self.roundtimes)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000580 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
Guido van Rossum486364b2007-06-30 05:01:58 +0000586 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()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000594 if self.machine_details:
595 print_machine_details(self.machine_details, indent=' ')
Guido van Rossum486364b2007-06-30 05:01:58 +0000596 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000597
598 def print_benchmark(self, hidenoise=0, limitnames=None):
599
Collin Winter6afaeb72007-08-03 17:06:41 +0000600 print('Test '
601 ' minimum average operation overhead')
Guido van Rossum486364b2007-06-30 05:01:58 +0000602 print('-' * LINE)
603 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000604 total_min_time = 0.0
605 total_avg_time = 0.0
606 for name, test in tests:
607 if (limitnames is not None and
608 limitnames.search(name) is None):
609 continue
610 (min_time,
611 avg_time,
612 total_time,
613 op_avg,
614 min_overhead) = test.stat()
615 total_min_time = total_min_time + min_time
616 total_avg_time = total_avg_time + avg_time
Guido van Rossum486364b2007-06-30 05:01:58 +0000617 print('%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000618 (name,
619 min_time * MILLI_SECONDS,
620 avg_time * MILLI_SECONDS,
621 op_avg * MICRO_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000622 min_overhead *MILLI_SECONDS))
623 print('-' * LINE)
Collin Winter6afaeb72007-08-03 17:06:41 +0000624 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000625 ' %6.0fms %6.0fms' %
626 (total_min_time * MILLI_SECONDS,
627 total_avg_time * MILLI_SECONDS,
Collin Winter6afaeb72007-08-03 17:06:41 +0000628 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000629 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000630
631 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
632
633 # Check benchmark versions
634 if compare_to.version != self.version:
Collin Winter6afaeb72007-08-03 17:06:41 +0000635 print('* Benchmark versions differ: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000636 'cannot compare this benchmark to "%s" !' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000637 compare_to.name)
Guido van Rossum486364b2007-06-30 05:01:58 +0000638 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000639 self.print_benchmark(hidenoise=hidenoise,
640 limitnames=limitnames)
641 return
642
643 # Print header
644 compare_to.print_header('Comparing with')
Collin Winter6afaeb72007-08-03 17:06:41 +0000645 print('Test '
646 ' minimum run-time average run-time')
647 print(' '
648 ' this other diff this other diff')
Guido van Rossum486364b2007-06-30 05:01:58 +0000649 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000650
651 # Print test comparisons
Guido van Rossum486364b2007-06-30 05:01:58 +0000652 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000653 total_min_time = other_total_min_time = 0.0
654 total_avg_time = other_total_avg_time = 0.0
655 benchmarks_compatible = self.compatible(compare_to)
656 tests_compatible = 1
657 for name, test in tests:
658 if (limitnames is not None and
659 limitnames.search(name) is None):
660 continue
661 (min_time,
662 avg_time,
663 total_time,
664 op_avg,
665 min_overhead) = test.stat()
666 total_min_time = total_min_time + min_time
667 total_avg_time = total_avg_time + avg_time
668 try:
669 other = compare_to.tests[name]
670 except KeyError:
671 other = None
672 if other is None:
673 # Other benchmark doesn't include the given test
674 min_diff, avg_diff = 'n/a', 'n/a'
675 other_min_time = 0.0
676 other_avg_time = 0.0
677 tests_compatible = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000678 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000679 (other_min_time,
680 other_avg_time,
681 other_total_time,
682 other_op_avg,
683 other_min_overhead) = other.stat()
684 other_total_min_time = other_total_min_time + other_min_time
685 other_total_avg_time = other_total_avg_time + other_avg_time
686 if (benchmarks_compatible and
687 test.compatible(other)):
688 # Both benchmark and tests are comparible
689 min_diff = ((min_time * self.warp) /
690 (other_min_time * other.warp) - 1.0)
691 avg_diff = ((avg_time * self.warp) /
692 (other_avg_time * other.warp) - 1.0)
693 if hidenoise and abs(min_diff) < 10.0:
694 min_diff = ''
695 else:
696 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
697 if hidenoise and abs(avg_diff) < 10.0:
698 avg_diff = ''
699 else:
700 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
701 else:
702 # Benchmark or tests are not comparible
703 min_diff, avg_diff = 'n/a', 'n/a'
704 tests_compatible = 0
Guido van Rossum486364b2007-06-30 05:01:58 +0000705 print('%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000706 (name,
707 min_time * MILLI_SECONDS,
708 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
709 min_diff,
710 avg_time * MILLI_SECONDS,
711 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
Guido van Rossum486364b2007-06-30 05:01:58 +0000712 avg_diff))
713 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000714
715 # Summarise test results
716 if not benchmarks_compatible or not tests_compatible:
717 min_diff, avg_diff = 'n/a', 'n/a'
718 else:
719 if other_total_min_time != 0.0:
720 min_diff = '%+5.1f%%' % (
721 ((total_min_time * self.warp) /
722 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
723 else:
724 min_diff = 'n/a'
725 if other_total_avg_time != 0.0:
726 avg_diff = '%+5.1f%%' % (
727 ((total_avg_time * self.warp) /
728 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
729 else:
730 avg_diff = 'n/a'
Collin Winter6afaeb72007-08-03 17:06:41 +0000731 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000732 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
733 (total_min_time * MILLI_SECONDS,
734 (other_total_min_time * compare_to.warp/self.warp
735 * MILLI_SECONDS),
736 min_diff,
737 total_avg_time * MILLI_SECONDS,
738 (other_total_avg_time * compare_to.warp/self.warp
739 * MILLI_SECONDS),
740 avg_diff
Collin Winter6afaeb72007-08-03 17:06:41 +0000741 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000742 print()
743 print('(this=%s, other=%s)' % (self.name,
744 compare_to.name))
745 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000746
747class PyBenchCmdline(Application):
748
749 header = ("PYBENCH - a benchmark test suite for Python "
750 "interpreters/compilers.")
751
752 version = __version__
753
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000754 debug = _debug
755
756 options = [ArgumentOption('-n',
757 'number of rounds',
758 Setup.Number_of_rounds),
759 ArgumentOption('-f',
760 'save benchmark to file arg',
761 ''),
762 ArgumentOption('-c',
763 'compare benchmark with the one in file arg',
764 ''),
765 ArgumentOption('-s',
766 'show benchmark in file arg, then exit',
767 ''),
768 ArgumentOption('-w',
769 'set warp factor to arg',
770 Setup.Warp_factor),
771 ArgumentOption('-t',
772 'run only tests with names matching arg',
773 ''),
774 ArgumentOption('-C',
775 'set the number of calibration runs to arg',
776 CALIBRATION_RUNS),
777 SwitchOption('-d',
778 'hide noise in comparisons',
779 0),
780 SwitchOption('-v',
781 'verbose output (not recommended)',
782 0),
783 SwitchOption('--with-gc',
784 'enable garbage collection',
785 0),
786 SwitchOption('--with-syscheck',
787 'use default sys check interval',
788 0),
789 ArgumentOption('--timer',
790 'use given timer',
791 TIMER_PLATFORM_DEFAULT),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000792 ]
793
794 about = """\
795The normal operation is to run the suite and display the
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000796results. Use -f to save them for later reuse or comparisons.
797
798Available timers:
799
800 time.time
801 time.clock
802 systimes.processtime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000803
804Examples:
805
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000806python2.1 pybench.py -f p21.pybench
807python2.5 pybench.py -f p25.pybench
808python pybench.py -s p25.pybench -c p21.pybench
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000809"""
810 copyright = __copyright__
811
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000812 def main(self):
813
814 rounds = self.values['-n']
815 reportfile = self.values['-f']
816 show_bench = self.values['-s']
817 compare_to = self.values['-c']
818 hidenoise = self.values['-d']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000819 warp = int(self.values['-w'])
820 withgc = self.values['--with-gc']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000821 limitnames = self.values['-t']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000822 if limitnames:
823 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000824 print('* limiting test names to one with substring "%s"' % \
825 limitnames)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000826 limitnames = re.compile(limitnames, re.I)
827 else:
828 limitnames = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000829 verbose = self.verbose
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000830 withsyscheck = self.values['--with-syscheck']
831 calibration_runs = self.values['-C']
832 timer = self.values['--timer']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000833
Guido van Rossum486364b2007-06-30 05:01:58 +0000834 print('-' * LINE)
835 print('PYBENCH %s' % __version__)
836 print('-' * LINE)
837 print('* using %s %s' % (
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000838 getattr(platform, 'python_implementation', lambda:'Python')(),
Guido van Rossum486364b2007-06-30 05:01:58 +0000839 ' '.join(sys.version.split())))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000840
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000841 # Switch off garbage collection
842 if not withgc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000843 try:
844 import gc
845 except ImportError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000846 print('* Python version doesn\'t support garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000847 else:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000848 try:
849 gc.disable()
850 except NotImplementedError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000851 print('* Python version doesn\'t support gc.disable')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000852 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000853 print('* disabled garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000854
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000855 # "Disable" sys check interval
856 if not withsyscheck:
857 # Too bad the check interval uses an int instead of a long...
858 value = 2147483647
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000859 try:
860 sys.setcheckinterval(value)
861 except (AttributeError, NotImplementedError):
Guido van Rossum486364b2007-06-30 05:01:58 +0000862 print('* Python version doesn\'t support sys.setcheckinterval')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000863 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000864 print('* system check interval set to maximum: %s' % value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000865
866 if timer == TIMER_SYSTIMES_PROCESSTIME:
867 import systimes
Guido van Rossum486364b2007-06-30 05:01:58 +0000868 print('* using timer: systimes.processtime (%s)' % \
869 systimes.SYSTIMES_IMPLEMENTATION)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000870 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000871 print('* using timer: %s' % timer)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000872
Guido van Rossum486364b2007-06-30 05:01:58 +0000873 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000874
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000875 if compare_to:
876 try:
877 f = open(compare_to,'rb')
878 bench = pickle.load(f)
879 bench.name = compare_to
880 f.close()
881 compare_to = bench
Guido van Rossumb940e112007-01-10 16:19:56 +0000882 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000883 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000884 repr(compare_to),
Guido van Rossum486364b2007-06-30 05:01:58 +0000885 reason))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000886 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000887
888 if show_bench:
889 try:
890 f = open(show_bench,'rb')
891 bench = pickle.load(f)
892 bench.name = show_bench
893 f.close()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000894 bench.print_header()
895 if compare_to:
896 bench.print_comparison(compare_to,
897 hidenoise=hidenoise,
898 limitnames=limitnames)
899 else:
900 bench.print_benchmark(hidenoise=hidenoise,
901 limitnames=limitnames)
Guido van Rossumb940e112007-01-10 16:19:56 +0000902 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000903 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000904 repr(show_bench),
Guido van Rossum486364b2007-06-30 05:01:58 +0000905 reason))
906 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000907 return
908
909 if reportfile:
Guido van Rossum486364b2007-06-30 05:01:58 +0000910 print('Creating benchmark: %s (rounds=%i, warp=%i)' % \
911 (reportfile, rounds, warp))
912 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000913
914 # Create benchmark object
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000915 bench = Benchmark(reportfile,
916 verbose=verbose,
917 timer=timer,
918 warp=warp,
919 calibration_runs=calibration_runs)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000920 bench.rounds = rounds
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000921 bench.load_tests(Setup, limitnames=limitnames)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000922 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000923 bench.calibrate()
924 bench.run()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000925 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000926 print()
927 print('*** KeyboardInterrupt -- Aborting')
928 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000929 return
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000930 bench.print_header()
931 if compare_to:
932 bench.print_comparison(compare_to,
933 hidenoise=hidenoise,
934 limitnames=limitnames)
935 else:
936 bench.print_benchmark(hidenoise=hidenoise,
937 limitnames=limitnames)
938
939 # Ring bell
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000940 sys.stderr.write('\007')
941
942 if reportfile:
943 try:
944 f = open(reportfile,'wb')
945 bench.name = reportfile
946 pickle.dump(bench,f)
947 f.close()
Guido van Rossumb940e112007-01-10 16:19:56 +0000948 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000949 print('* Error opening/writing reportfile')
Guido van Rossumb940e112007-01-10 16:19:56 +0000950 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000951 print('* Error opening/writing reportfile %s: %s' % (
Thomas Wouters89f507f2006-12-13 04:49:30 +0000952 reportfile,
Guido van Rossum486364b2007-06-30 05:01:58 +0000953 reason))
954 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000955
956if __name__ == '__main__':
957 PyBenchCmdline()