blob: 8eaad63cf1e8d136cdeb6d9955e27c09445f0a1e [file] [log] [blame]
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00001#!/usr/local/bin/python -O
2
3""" A Python Benchmark Suite
4
5"""
Antoine Pitrou8a681222009-02-07 17:13:31 +00006# Note: Please keep this module compatible to Python 2.6.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +00007#
8# Tests may include features in later Python versions, but these
9# should then be embedded in try-except clauses in the configuration
10# module Setup.py.
11#
12
Antoine Pitrou8a681222009-02-07 17:13:31 +000013from __future__ import print_function
14
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000015# pybench Copyright
16__copyright__ = """\
17Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com)
18Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com)
19
20 All Rights Reserved.
21
22Permission to use, copy, modify, and distribute this software and its
23documentation for any purpose and without fee or royalty is hereby
24granted, provided that the above copyright notice appear in all copies
25and that both that copyright notice and this permission notice appear
26in supporting documentation or portions thereof, including
27modifications, that you make.
28
29THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO
30THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
31FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
32INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
33FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
34NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
35WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
36"""
37
Guido van Rossum486364b2007-06-30 05:01:58 +000038import sys, time, operator, platform
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000039from CommandLine import *
40
41try:
42 import cPickle
43 pickle = cPickle
44except ImportError:
45 import pickle
46
Thomas Wouters0e3f5912006-08-11 14:57:12 +000047# Version number; version history: see README file !
Antoine Pitrou8a681222009-02-07 17:13:31 +000048__version__ = '2.1'
Thomas Wouters0e3f5912006-08-11 14:57:12 +000049
50### Constants
51
52# Second fractions
53MILLI_SECONDS = 1e3
54MICRO_SECONDS = 1e6
55
56# Percent unit
57PERCENT = 100
58
59# Horizontal line length
60LINE = 79
61
62# Minimum test run-time
63MIN_TEST_RUNTIME = 1e-3
64
65# Number of calibration runs to use for calibrating the tests
66CALIBRATION_RUNS = 20
67
68# Number of calibration loops to run for each calibration run
69CALIBRATION_LOOPS = 20
70
71# Allow skipping calibration ?
72ALLOW_SKIPPING_CALIBRATION = 1
73
74# Timer types
75TIMER_TIME_TIME = 'time.time'
76TIMER_TIME_CLOCK = 'time.clock'
77TIMER_SYSTIMES_PROCESSTIME = 'systimes.processtime'
78
79# Choose platform default timer
80if sys.platform[:3] == 'win':
81 # On WinXP this has 2.5ms resolution
82 TIMER_PLATFORM_DEFAULT = TIMER_TIME_CLOCK
83else:
84 # On Linux this has 1ms resolution
85 TIMER_PLATFORM_DEFAULT = TIMER_TIME_TIME
86
87# Print debug information ?
88_debug = 0
89
90### Helpers
91
92def get_timer(timertype):
93
94 if timertype == TIMER_TIME_TIME:
95 return time.time
96 elif timertype == TIMER_TIME_CLOCK:
97 return time.clock
98 elif timertype == TIMER_SYSTIMES_PROCESSTIME:
99 import systimes
100 return systimes.processtime
101 else:
102 raise TypeError('unknown timer type: %s' % timertype)
103
104def get_machine_details():
105
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000106 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000107 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000108 buildno, builddate = platform.python_build()
109 python = platform.python_version()
Antoine Pitrou251803b2008-07-22 18:03:03 +0000110 if sys.maxunicode == 65535:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000111 # UCS2 build (standard)
Georg Brandlbf82e372008-05-16 17:02:34 +0000112 unitype = 'UCS2'
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000113 else:
114 # UCS4 build (most recent Linux distros)
Georg Brandlbf82e372008-05-16 17:02:34 +0000115 unitype = 'UCS4'
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000116 bits, linkage = platform.architecture()
117 return {
118 'platform': platform.platform(),
119 'processor': platform.processor(),
120 'executable': sys.executable,
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000121 'implementation': getattr(platform, 'python_implementation',
122 lambda:'n/a')(),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000123 'python': platform.python_version(),
124 'compiler': platform.python_compiler(),
125 'buildno': buildno,
126 'builddate': builddate,
Georg Brandlbf82e372008-05-16 17:02:34 +0000127 'unicode': unitype,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000128 'bits': bits,
129 }
130
131def print_machine_details(d, indent=''):
132
133 l = ['Machine Details:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000134 ' Platform ID: %s' % d.get('platform', 'n/a'),
135 ' Processor: %s' % d.get('processor', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000136 '',
137 'Python:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000138 ' Implementation: %s' % d.get('implementation', 'n/a'),
139 ' Executable: %s' % d.get('executable', 'n/a'),
140 ' Version: %s' % d.get('python', 'n/a'),
141 ' Compiler: %s' % d.get('compiler', 'n/a'),
142 ' Bits: %s' % d.get('bits', 'n/a'),
143 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
144 d.get('buildno', 'n/a')),
145 ' Unicode: %s' % d.get('unicode', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000146 ]
Guido van Rossum486364b2007-06-30 05:01:58 +0000147 joiner = '\n' + indent
148 print(indent + joiner.join(l) + '\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000149
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000150### Test baseclass
151
152class Test:
153
154 """ All test must have this class as baseclass. It provides
155 the necessary interface to the benchmark machinery.
156
157 The tests must set .rounds to a value high enough to let the
158 test run between 20-50 seconds. This is needed because
159 clock()-timing only gives rather inaccurate values (on Linux,
160 for example, it is accurate to a few hundreths of a
161 second). If you don't want to wait that long, use a warp
162 factor larger than 1.
163
164 It is also important to set the .operations variable to a
165 value representing the number of "virtual operations" done per
166 call of .run().
167
168 If you change a test in some way, don't forget to increase
Guido van Rossumd8faa362007-04-27 19:54:29 +0000169 its version number.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000170
171 """
172
173 ### Instance variables that each test should override
174
175 # Version number of the test as float (x.yy); this is important
176 # for comparisons of benchmark runs - tests with unequal version
177 # number will not get compared.
Antoine Pitrou8a681222009-02-07 17:13:31 +0000178 version = 2.1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000179
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000180 # The number of abstract operations done in each round of the
181 # test. An operation is the basic unit of what you want to
182 # measure. The benchmark will output the amount of run-time per
183 # operation. Note that in order to raise the measured timings
184 # significantly above noise level, it is often required to repeat
185 # sets of operations more than once per test round. The measured
186 # overhead per test round should be less than 1 second.
187 operations = 1
188
189 # Number of rounds to execute per test run. This should be
190 # adjusted to a figure that results in a test run-time of between
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000191 # 1-2 seconds.
192 rounds = 100000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000193
194 ### Internal variables
195
196 # Mark this class as implementing a test
197 is_a_test = 1
198
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000199 # Last timing: (real, run, overhead)
200 last_timing = (0.0, 0.0, 0.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000201
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000202 # Warp factor to use for this test
203 warp = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000204
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000205 # Number of calibration runs to use
206 calibration_runs = CALIBRATION_RUNS
207
208 # List of calibration timings
209 overhead_times = None
210
211 # List of test run timings
212 times = []
213
214 # Timer used for the benchmark
215 timer = TIMER_PLATFORM_DEFAULT
216
217 def __init__(self, warp=None, calibration_runs=None, timer=None):
218
219 # Set parameters
220 if warp is not None:
221 self.rounds = int(self.rounds / warp)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000222 if self.rounds == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000223 raise ValueError('warp factor set too high')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000224 self.warp = warp
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000225 if calibration_runs is not None:
226 if (not ALLOW_SKIPPING_CALIBRATION and
227 calibration_runs < 1):
228 raise ValueError('at least one calibration run is required')
229 self.calibration_runs = calibration_runs
230 if timer is not None:
Benjamin Petersonf6489f92009-11-25 17:46:26 +0000231 self.timer = timer
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000232
233 # Init variables
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000234 self.times = []
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000235 self.overhead_times = []
236
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000237 # We want these to be in the instance dict, so that pickle
238 # saves them
239 self.version = self.version
240 self.operations = self.operations
241 self.rounds = self.rounds
242
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000243 def get_timer(self):
244
245 """ Return the timer function to use for the test.
246
247 """
248 return get_timer(self.timer)
249
250 def compatible(self, other):
251
252 """ Return 1/0 depending on whether the test is compatible
253 with the other Test instance or not.
254
255 """
256 if self.version != other.version:
257 return 0
258 if self.rounds != other.rounds:
259 return 0
260 return 1
261
262 def calibrate_test(self):
263
264 if self.calibration_runs == 0:
265 self.overhead_times = [0.0]
266 return
267
268 calibrate = self.calibrate
269 timer = self.get_timer()
270 calibration_loops = range(CALIBRATION_LOOPS)
271
272 # Time the calibration loop overhead
273 prep_times = []
274 for i in range(self.calibration_runs):
275 t = timer()
276 for i in calibration_loops:
277 pass
278 t = timer() - t
Jesus Cea8f14bbd2011-04-25 03:24:08 +0200279 prep_times.append(t / CALIBRATION_LOOPS)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000280 min_prep_time = min(prep_times)
281 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000282 print()
283 print('Calib. prep time = %.6fms' % (
284 min_prep_time * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000285
286 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
287 # .calibrate() method calls each)
288 for i in range(self.calibration_runs):
289 t = timer()
290 for i in calibration_loops:
291 calibrate()
292 t = timer() - t
293 self.overhead_times.append(t / CALIBRATION_LOOPS
294 - min_prep_time)
295
296 # Check the measured times
297 min_overhead = min(self.overhead_times)
298 max_overhead = max(self.overhead_times)
299 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000300 print('Calib. overhead time = %.6fms' % (
301 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000302 if min_overhead < 0.0:
303 raise ValueError('calibration setup did not work')
304 if max_overhead - min_overhead > 0.1:
305 raise ValueError(
306 'overhead calibration timing range too inaccurate: '
307 '%r - %r' % (min_overhead, max_overhead))
308
309 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000310
311 """ Run the test in two phases: first calibrate, then
312 do the actual test. Be careful to keep the calibration
313 timing low w/r to the test timing.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000314
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000315 """
316 test = self.test
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000317 timer = self.get_timer()
318
319 # Get calibration
320 min_overhead = min(self.overhead_times)
321
322 # Test run
323 t = timer()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000324 test()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000325 t = timer() - t
326 if t < MIN_TEST_RUNTIME:
327 raise ValueError('warp factor too high: '
328 'test times are < 10ms')
329 eff_time = t - min_overhead
330 if eff_time < 0:
331 raise ValueError('wrong calibration')
332 self.last_timing = (eff_time, t, min_overhead)
333 self.times.append(eff_time)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000334
335 def calibrate(self):
336
Thomas Wouters477c8d52006-05-27 19:21:47 +0000337 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000338
Thomas Wouters477c8d52006-05-27 19:21:47 +0000339 This method should execute everything that is needed to
340 setup and run the test - except for the actual operations
341 that you intend to measure. pybench uses this method to
342 measure the test implementation overhead.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000343
344 """
345 return
346
347 def test(self):
348
Thomas Wouters477c8d52006-05-27 19:21:47 +0000349 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000350
Thomas Wouters477c8d52006-05-27 19:21:47 +0000351 The test needs to run self.rounds executing
352 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000353
354 """
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000355 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000356
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000357 def stat(self):
358
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000359 """ Return test run statistics as tuple:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000361 (minimum run time,
362 average run time,
363 total run time,
364 average time per operation,
365 minimum overhead time)
366
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000367 """
368 runs = len(self.times)
369 if runs == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000370 return 0.0, 0.0, 0.0, 0.0
371 min_time = min(self.times)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000372 total_time = sum(self.times)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000373 avg_time = total_time / float(runs)
374 operation_avg = total_time / float(runs
375 * self.rounds
376 * self.operations)
377 if self.overhead_times:
378 min_overhead = min(self.overhead_times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000379 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000380 min_overhead = self.last_timing[2]
381 return min_time, avg_time, total_time, operation_avg, min_overhead
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000382
383### Load Setup
384
385# This has to be done after the definition of the Test class, since
386# the Setup module will import subclasses using this class.
387
388import Setup
389
390### Benchmark base class
391
392class Benchmark:
393
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000394 # Name of the benchmark
395 name = ''
396
397 # Number of benchmark rounds to run
398 rounds = 1
399
400 # Warp factor use to run the tests
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000401 warp = 1 # Warp factor
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000402
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000403 # Average benchmark round time
404 roundtime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000405
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000406 # Benchmark version number as float x.yy
Antoine Pitrou8a681222009-02-07 17:13:31 +0000407 version = 2.1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000408
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000409 # Produce verbose output ?
410 verbose = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000411
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412 # Dictionary with the machine details
413 machine_details = None
414
415 # Timer used for the benchmark
416 timer = TIMER_PLATFORM_DEFAULT
417
418 def __init__(self, name, verbose=None, timer=None, warp=None,
419 calibration_runs=None):
420
421 if name:
422 self.name = name
Thomas Wouters477c8d52006-05-27 19:21:47 +0000423 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000424 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
425 (time.localtime(time.time())[:6])
426 if verbose is not None:
427 self.verbose = verbose
428 if timer is not None:
429 self.timer = timer
430 if warp is not None:
431 self.warp = warp
432 if calibration_runs is not None:
433 self.calibration_runs = calibration_runs
434
435 # Init vars
436 self.tests = {}
437 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000438 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000439 self.machine_details = get_machine_details()
440
441 # Make .version an instance attribute to have it saved in the
442 # Benchmark pickle
443 self.version = self.version
444
445 def get_timer(self):
446
447 """ Return the timer function to use for the test.
448
449 """
450 return get_timer(self.timer)
451
452 def compatible(self, other):
453
454 """ Return 1/0 depending on whether the benchmark is
455 compatible with the other Benchmark instance or not.
456
457 """
458 if self.version != other.version:
459 return 0
460 if (self.machine_details == other.machine_details and
461 self.timer != other.timer):
462 return 0
463 if (self.calibration_runs == 0 and
464 other.calibration_runs != 0):
465 return 0
466 if (self.calibration_runs != 0 and
467 other.calibration_runs == 0):
468 return 0
469 return 1
470
471 def load_tests(self, setupmod, limitnames=None):
472
473 # Add tests
474 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000475 print('Searching for tests ...')
476 print('--------------------------------------')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000477 for testclass in setupmod.__dict__.values():
478 if not hasattr(testclass, 'is_a_test'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000479 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000480 name = testclass.__name__
Thomas Wouters477c8d52006-05-27 19:21:47 +0000481 if name == 'Test':
482 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000483 if (limitnames is not None and
484 limitnames.search(name) is None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000485 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000486 self.tests[name] = testclass(
487 warp=self.warp,
488 calibration_runs=self.calibration_runs,
489 timer=self.timer)
Guido van Rossum486364b2007-06-30 05:01:58 +0000490 l = sorted(self.tests)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000491 if self.verbose:
492 for name in l:
Guido van Rossum486364b2007-06-30 05:01:58 +0000493 print(' %s' % name)
494 print('--------------------------------------')
495 print(' %i tests found' % len(l))
496 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000497
498 def calibrate(self):
499
Guido van Rossum486364b2007-06-30 05:01:58 +0000500 print('Calibrating tests. Please wait...', end=' ')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000501 sys.stdout.flush()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000502 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000503 print()
504 print()
505 print('Test min max')
506 print('-' * LINE)
507 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000508 for i in range(len(tests)):
509 name, test = tests[i]
510 test.calibrate_test()
511 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000512 print('%30s: %6.3fms %6.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000513 (name,
514 min(test.overhead_times) * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000515 max(test.overhead_times) * MILLI_SECONDS))
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000516 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000517 print()
518 print('Done with the calibration.')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000519 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000520 print('done.')
521 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000522
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000523 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000524
Guido van Rossum486364b2007-06-30 05:01:58 +0000525 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000526 timer = self.get_timer()
Guido van Rossum486364b2007-06-30 05:01:58 +0000527 print('Running %i round(s) of the suite at warp factor %i:' % \
528 (self.rounds, self.warp))
529 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000530 self.roundtimes = []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000531 for i in range(self.rounds):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000532 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000533 print(' Round %-25i effective absolute overhead' % (i+1))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000534 total_eff_time = 0.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000535 for j in range(len(tests)):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000536 name, test = tests[j]
537 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000538 print('%30s:' % name, end=' ')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000539 test.run()
540 (eff_time, abs_time, min_overhead) = test.last_timing
541 total_eff_time = total_eff_time + eff_time
542 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000543 print(' %5.0fms %5.0fms %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000544 (eff_time * MILLI_SECONDS,
545 abs_time * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000546 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 self.roundtimes.append(total_eff_time)
548 if self.verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +0000549 print(' '
550 ' ------------------------------')
551 print(' '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000552 ' Totals: %6.0fms' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000553 (total_eff_time * MILLI_SECONDS))
Guido van Rossum486364b2007-06-30 05:01:58 +0000554 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000555 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000556 print('* Round %i done in %.3f seconds.' % (i+1,
557 total_eff_time))
558 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000559
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000560 def stat(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000561
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000562 """ Return benchmark run statistics as tuple:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000563
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000564 (minimum round time,
565 average round time,
566 maximum round time)
567
568 XXX Currently not used, since the benchmark does test
569 statistics across all rounds.
570
571 """
572 runs = len(self.roundtimes)
573 if runs == 0:
574 return 0.0, 0.0
575 min_time = min(self.roundtimes)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000576 total_time = sum(self.roundtimes)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000577 avg_time = total_time / float(runs)
578 max_time = max(self.roundtimes)
579 return (min_time, avg_time, max_time)
580
581 def print_header(self, title='Benchmark'):
582
Guido van Rossum486364b2007-06-30 05:01:58 +0000583 print('-' * LINE)
584 print('%s: %s' % (title, self.name))
585 print('-' * LINE)
586 print()
587 print(' Rounds: %s' % self.rounds)
588 print(' Warp: %s' % self.warp)
589 print(' Timer: %s' % self.timer)
590 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000591 if self.machine_details:
592 print_machine_details(self.machine_details, indent=' ')
Guido van Rossum486364b2007-06-30 05:01:58 +0000593 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000594
595 def print_benchmark(self, hidenoise=0, limitnames=None):
596
Collin Winter6afaeb72007-08-03 17:06:41 +0000597 print('Test '
598 ' minimum average operation overhead')
Guido van Rossum486364b2007-06-30 05:01:58 +0000599 print('-' * LINE)
600 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000601 total_min_time = 0.0
602 total_avg_time = 0.0
603 for name, test in tests:
604 if (limitnames is not None and
605 limitnames.search(name) is None):
606 continue
607 (min_time,
608 avg_time,
609 total_time,
610 op_avg,
611 min_overhead) = test.stat()
612 total_min_time = total_min_time + min_time
613 total_avg_time = total_avg_time + avg_time
Guido van Rossum486364b2007-06-30 05:01:58 +0000614 print('%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000615 (name,
616 min_time * MILLI_SECONDS,
617 avg_time * MILLI_SECONDS,
618 op_avg * MICRO_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000619 min_overhead *MILLI_SECONDS))
620 print('-' * LINE)
Collin Winter6afaeb72007-08-03 17:06:41 +0000621 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000622 ' %6.0fms %6.0fms' %
623 (total_min_time * MILLI_SECONDS,
624 total_avg_time * MILLI_SECONDS,
Collin Winter6afaeb72007-08-03 17:06:41 +0000625 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000626 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000627
628 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
629
630 # Check benchmark versions
631 if compare_to.version != self.version:
Collin Winter6afaeb72007-08-03 17:06:41 +0000632 print('* Benchmark versions differ: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000633 'cannot compare this benchmark to "%s" !' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000634 compare_to.name)
Guido van Rossum486364b2007-06-30 05:01:58 +0000635 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000636 self.print_benchmark(hidenoise=hidenoise,
637 limitnames=limitnames)
638 return
639
640 # Print header
641 compare_to.print_header('Comparing with')
Collin Winter6afaeb72007-08-03 17:06:41 +0000642 print('Test '
643 ' minimum run-time average run-time')
644 print(' '
645 ' this other diff this other diff')
Guido van Rossum486364b2007-06-30 05:01:58 +0000646 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000647
648 # Print test comparisons
Guido van Rossum486364b2007-06-30 05:01:58 +0000649 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000650 total_min_time = other_total_min_time = 0.0
651 total_avg_time = other_total_avg_time = 0.0
652 benchmarks_compatible = self.compatible(compare_to)
653 tests_compatible = 1
654 for name, test in tests:
655 if (limitnames is not None and
656 limitnames.search(name) is None):
657 continue
658 (min_time,
659 avg_time,
660 total_time,
661 op_avg,
662 min_overhead) = test.stat()
663 total_min_time = total_min_time + min_time
664 total_avg_time = total_avg_time + avg_time
665 try:
666 other = compare_to.tests[name]
667 except KeyError:
668 other = None
669 if other is None:
670 # Other benchmark doesn't include the given test
671 min_diff, avg_diff = 'n/a', 'n/a'
672 other_min_time = 0.0
673 other_avg_time = 0.0
674 tests_compatible = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000675 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000676 (other_min_time,
677 other_avg_time,
678 other_total_time,
679 other_op_avg,
680 other_min_overhead) = other.stat()
681 other_total_min_time = other_total_min_time + other_min_time
682 other_total_avg_time = other_total_avg_time + other_avg_time
683 if (benchmarks_compatible and
684 test.compatible(other)):
Ezio Melotti42da6632011-03-15 05:18:48 +0200685 # Both benchmark and tests are comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000686 min_diff = ((min_time * self.warp) /
687 (other_min_time * other.warp) - 1.0)
688 avg_diff = ((avg_time * self.warp) /
689 (other_avg_time * other.warp) - 1.0)
690 if hidenoise and abs(min_diff) < 10.0:
691 min_diff = ''
692 else:
693 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
694 if hidenoise and abs(avg_diff) < 10.0:
695 avg_diff = ''
696 else:
697 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
698 else:
Ezio Melotti42da6632011-03-15 05:18:48 +0200699 # Benchmark or tests are not comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000700 min_diff, avg_diff = 'n/a', 'n/a'
701 tests_compatible = 0
Guido van Rossum486364b2007-06-30 05:01:58 +0000702 print('%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000703 (name,
704 min_time * MILLI_SECONDS,
705 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
706 min_diff,
707 avg_time * MILLI_SECONDS,
708 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
Guido van Rossum486364b2007-06-30 05:01:58 +0000709 avg_diff))
710 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000711
712 # Summarise test results
713 if not benchmarks_compatible or not tests_compatible:
714 min_diff, avg_diff = 'n/a', 'n/a'
715 else:
716 if other_total_min_time != 0.0:
717 min_diff = '%+5.1f%%' % (
718 ((total_min_time * self.warp) /
719 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
720 else:
721 min_diff = 'n/a'
722 if other_total_avg_time != 0.0:
723 avg_diff = '%+5.1f%%' % (
724 ((total_avg_time * self.warp) /
725 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
726 else:
727 avg_diff = 'n/a'
Collin Winter6afaeb72007-08-03 17:06:41 +0000728 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000729 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
730 (total_min_time * MILLI_SECONDS,
731 (other_total_min_time * compare_to.warp/self.warp
732 * MILLI_SECONDS),
733 min_diff,
734 total_avg_time * MILLI_SECONDS,
735 (other_total_avg_time * compare_to.warp/self.warp
736 * MILLI_SECONDS),
737 avg_diff
Collin Winter6afaeb72007-08-03 17:06:41 +0000738 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000739 print()
740 print('(this=%s, other=%s)' % (self.name,
741 compare_to.name))
742 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000743
744class PyBenchCmdline(Application):
745
746 header = ("PYBENCH - a benchmark test suite for Python "
747 "interpreters/compilers.")
748
749 version = __version__
750
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000751 debug = _debug
752
753 options = [ArgumentOption('-n',
754 'number of rounds',
755 Setup.Number_of_rounds),
756 ArgumentOption('-f',
757 'save benchmark to file arg',
758 ''),
759 ArgumentOption('-c',
760 'compare benchmark with the one in file arg',
761 ''),
762 ArgumentOption('-s',
763 'show benchmark in file arg, then exit',
764 ''),
765 ArgumentOption('-w',
766 'set warp factor to arg',
767 Setup.Warp_factor),
768 ArgumentOption('-t',
769 'run only tests with names matching arg',
770 ''),
771 ArgumentOption('-C',
772 'set the number of calibration runs to arg',
773 CALIBRATION_RUNS),
774 SwitchOption('-d',
775 'hide noise in comparisons',
776 0),
777 SwitchOption('-v',
778 'verbose output (not recommended)',
779 0),
780 SwitchOption('--with-gc',
781 'enable garbage collection',
782 0),
783 SwitchOption('--with-syscheck',
784 'use default sys check interval',
785 0),
786 ArgumentOption('--timer',
787 'use given timer',
788 TIMER_PLATFORM_DEFAULT),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000789 ]
790
791 about = """\
792The normal operation is to run the suite and display the
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000793results. Use -f to save them for later reuse or comparisons.
794
795Available timers:
796
797 time.time
798 time.clock
799 systimes.processtime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000800
801Examples:
802
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000803python2.1 pybench.py -f p21.pybench
804python2.5 pybench.py -f p25.pybench
805python pybench.py -s p25.pybench -c p21.pybench
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000806"""
807 copyright = __copyright__
808
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000809 def main(self):
810
811 rounds = self.values['-n']
812 reportfile = self.values['-f']
813 show_bench = self.values['-s']
814 compare_to = self.values['-c']
815 hidenoise = self.values['-d']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000816 warp = int(self.values['-w'])
817 withgc = self.values['--with-gc']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000818 limitnames = self.values['-t']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000819 if limitnames:
820 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000821 print('* limiting test names to one with substring "%s"' % \
822 limitnames)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000823 limitnames = re.compile(limitnames, re.I)
824 else:
825 limitnames = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000826 verbose = self.verbose
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000827 withsyscheck = self.values['--with-syscheck']
828 calibration_runs = self.values['-C']
829 timer = self.values['--timer']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000830
Guido van Rossum486364b2007-06-30 05:01:58 +0000831 print('-' * LINE)
832 print('PYBENCH %s' % __version__)
833 print('-' * LINE)
834 print('* using %s %s' % (
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000835 getattr(platform, 'python_implementation', lambda:'Python')(),
Guido van Rossum486364b2007-06-30 05:01:58 +0000836 ' '.join(sys.version.split())))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000837
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000838 # Switch off garbage collection
839 if not withgc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000840 try:
841 import gc
842 except ImportError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000843 print('* Python version doesn\'t support garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000844 else:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000845 try:
846 gc.disable()
847 except NotImplementedError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000848 print('* Python version doesn\'t support gc.disable')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000849 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000850 print('* disabled garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000851
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000852 # "Disable" sys check interval
853 if not withsyscheck:
854 # Too bad the check interval uses an int instead of a long...
855 value = 2147483647
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000856 try:
857 sys.setcheckinterval(value)
858 except (AttributeError, NotImplementedError):
Guido van Rossum486364b2007-06-30 05:01:58 +0000859 print('* Python version doesn\'t support sys.setcheckinterval')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000860 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000861 print('* system check interval set to maximum: %s' % value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000862
863 if timer == TIMER_SYSTIMES_PROCESSTIME:
864 import systimes
Guido van Rossum486364b2007-06-30 05:01:58 +0000865 print('* using timer: systimes.processtime (%s)' % \
866 systimes.SYSTIMES_IMPLEMENTATION)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000867 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000868 print('* using timer: %s' % timer)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000869
Guido van Rossum486364b2007-06-30 05:01:58 +0000870 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000871
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000872 if compare_to:
873 try:
874 f = open(compare_to,'rb')
875 bench = pickle.load(f)
876 bench.name = compare_to
877 f.close()
878 compare_to = bench
Guido van Rossumb940e112007-01-10 16:19:56 +0000879 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000880 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000881 repr(compare_to),
Guido van Rossum486364b2007-06-30 05:01:58 +0000882 reason))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000883 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000884
885 if show_bench:
886 try:
887 f = open(show_bench,'rb')
888 bench = pickle.load(f)
889 bench.name = show_bench
890 f.close()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000891 bench.print_header()
892 if compare_to:
893 bench.print_comparison(compare_to,
894 hidenoise=hidenoise,
895 limitnames=limitnames)
896 else:
897 bench.print_benchmark(hidenoise=hidenoise,
898 limitnames=limitnames)
Guido van Rossumb940e112007-01-10 16:19:56 +0000899 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000900 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000901 repr(show_bench),
Guido van Rossum486364b2007-06-30 05:01:58 +0000902 reason))
903 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000904 return
905
906 if reportfile:
Guido van Rossum486364b2007-06-30 05:01:58 +0000907 print('Creating benchmark: %s (rounds=%i, warp=%i)' % \
908 (reportfile, rounds, warp))
909 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000910
911 # Create benchmark object
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000912 bench = Benchmark(reportfile,
913 verbose=verbose,
914 timer=timer,
915 warp=warp,
916 calibration_runs=calibration_runs)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000917 bench.rounds = rounds
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000918 bench.load_tests(Setup, limitnames=limitnames)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000919 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000920 bench.calibrate()
921 bench.run()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000922 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000923 print()
924 print('*** KeyboardInterrupt -- Aborting')
925 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000926 return
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000927 bench.print_header()
928 if compare_to:
929 bench.print_comparison(compare_to,
930 hidenoise=hidenoise,
931 limitnames=limitnames)
932 else:
933 bench.print_benchmark(hidenoise=hidenoise,
934 limitnames=limitnames)
935
936 # Ring bell
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000937 sys.stderr.write('\007')
938
939 if reportfile:
940 try:
941 f = open(reportfile,'wb')
942 bench.name = reportfile
943 pickle.dump(bench,f)
944 f.close()
Guido van Rossumb940e112007-01-10 16:19:56 +0000945 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000946 print('* Error opening/writing reportfile')
Guido van Rossumb940e112007-01-10 16:19:56 +0000947 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000948 print('* Error opening/writing reportfile %s: %s' % (
Thomas Wouters89f507f2006-12-13 04:49:30 +0000949 reportfile,
Guido van Rossum486364b2007-06-30 05:01:58 +0000950 reason))
951 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000952
953if __name__ == '__main__':
954 PyBenchCmdline()