blob: cc1e55c77184bb565ec4b7b5492052d3b7e7ddfd [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()
Ezio Melottia9860ae2011-10-04 19:06:00 +0300110 # XXX this is now always UCS4, maybe replace it with 'PEP393' in 3.3+?
Antoine Pitrou251803b2008-07-22 18:03:03 +0000111 if sys.maxunicode == 65535:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000112 # UCS2 build (standard)
Georg Brandlbf82e372008-05-16 17:02:34 +0000113 unitype = 'UCS2'
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000114 else:
115 # UCS4 build (most recent Linux distros)
Georg Brandlbf82e372008-05-16 17:02:34 +0000116 unitype = 'UCS4'
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000117 bits, linkage = platform.architecture()
118 return {
119 'platform': platform.platform(),
120 'processor': platform.processor(),
121 'executable': sys.executable,
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000122 'implementation': getattr(platform, 'python_implementation',
123 lambda:'n/a')(),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000124 'python': platform.python_version(),
125 'compiler': platform.python_compiler(),
126 'buildno': buildno,
127 'builddate': builddate,
Georg Brandlbf82e372008-05-16 17:02:34 +0000128 'unicode': unitype,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000129 'bits': bits,
130 }
131
132def print_machine_details(d, indent=''):
133
134 l = ['Machine Details:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000135 ' Platform ID: %s' % d.get('platform', 'n/a'),
136 ' Processor: %s' % d.get('processor', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000137 '',
138 'Python:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000139 ' Implementation: %s' % d.get('implementation', 'n/a'),
140 ' Executable: %s' % d.get('executable', 'n/a'),
141 ' Version: %s' % d.get('python', 'n/a'),
142 ' Compiler: %s' % d.get('compiler', 'n/a'),
143 ' Bits: %s' % d.get('bits', 'n/a'),
144 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
145 d.get('buildno', 'n/a')),
146 ' Unicode: %s' % d.get('unicode', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000147 ]
Guido van Rossum486364b2007-06-30 05:01:58 +0000148 joiner = '\n' + indent
149 print(indent + joiner.join(l) + '\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000150
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000151### Test baseclass
152
153class Test:
154
155 """ All test must have this class as baseclass. It provides
156 the necessary interface to the benchmark machinery.
157
158 The tests must set .rounds to a value high enough to let the
159 test run between 20-50 seconds. This is needed because
160 clock()-timing only gives rather inaccurate values (on Linux,
161 for example, it is accurate to a few hundreths of a
162 second). If you don't want to wait that long, use a warp
163 factor larger than 1.
164
165 It is also important to set the .operations variable to a
166 value representing the number of "virtual operations" done per
167 call of .run().
168
169 If you change a test in some way, don't forget to increase
Guido van Rossumd8faa362007-04-27 19:54:29 +0000170 its version number.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000171
172 """
173
174 ### Instance variables that each test should override
175
176 # Version number of the test as float (x.yy); this is important
177 # for comparisons of benchmark runs - tests with unequal version
178 # number will not get compared.
Antoine Pitrou8a681222009-02-07 17:13:31 +0000179 version = 2.1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000180
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000181 # The number of abstract operations done in each round of the
182 # test. An operation is the basic unit of what you want to
183 # measure. The benchmark will output the amount of run-time per
184 # operation. Note that in order to raise the measured timings
185 # significantly above noise level, it is often required to repeat
186 # sets of operations more than once per test round. The measured
187 # overhead per test round should be less than 1 second.
188 operations = 1
189
190 # Number of rounds to execute per test run. This should be
191 # adjusted to a figure that results in a test run-time of between
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000192 # 1-2 seconds.
193 rounds = 100000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000194
195 ### Internal variables
196
197 # Mark this class as implementing a test
198 is_a_test = 1
199
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000200 # Last timing: (real, run, overhead)
201 last_timing = (0.0, 0.0, 0.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000202
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000203 # Warp factor to use for this test
204 warp = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000205
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000206 # Number of calibration runs to use
207 calibration_runs = CALIBRATION_RUNS
208
209 # List of calibration timings
210 overhead_times = None
211
212 # List of test run timings
213 times = []
214
215 # Timer used for the benchmark
216 timer = TIMER_PLATFORM_DEFAULT
217
218 def __init__(self, warp=None, calibration_runs=None, timer=None):
219
220 # Set parameters
221 if warp is not None:
222 self.rounds = int(self.rounds / warp)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000223 if self.rounds == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000224 raise ValueError('warp factor set too high')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000225 self.warp = warp
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000226 if calibration_runs is not None:
227 if (not ALLOW_SKIPPING_CALIBRATION and
228 calibration_runs < 1):
229 raise ValueError('at least one calibration run is required')
230 self.calibration_runs = calibration_runs
231 if timer is not None:
Benjamin Petersonf6489f92009-11-25 17:46:26 +0000232 self.timer = timer
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000233
234 # Init variables
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000235 self.times = []
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000236 self.overhead_times = []
237
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000238 # We want these to be in the instance dict, so that pickle
239 # saves them
240 self.version = self.version
241 self.operations = self.operations
242 self.rounds = self.rounds
243
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000244 def get_timer(self):
245
246 """ Return the timer function to use for the test.
247
248 """
249 return get_timer(self.timer)
250
251 def compatible(self, other):
252
253 """ Return 1/0 depending on whether the test is compatible
254 with the other Test instance or not.
255
256 """
257 if self.version != other.version:
258 return 0
259 if self.rounds != other.rounds:
260 return 0
261 return 1
262
263 def calibrate_test(self):
264
265 if self.calibration_runs == 0:
266 self.overhead_times = [0.0]
267 return
268
269 calibrate = self.calibrate
270 timer = self.get_timer()
271 calibration_loops = range(CALIBRATION_LOOPS)
272
273 # Time the calibration loop overhead
274 prep_times = []
275 for i in range(self.calibration_runs):
276 t = timer()
277 for i in calibration_loops:
278 pass
279 t = timer() - t
Jesus Cea8f14bbd2011-04-25 03:24:08 +0200280 prep_times.append(t / CALIBRATION_LOOPS)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000281 min_prep_time = min(prep_times)
282 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000283 print()
284 print('Calib. prep time = %.6fms' % (
285 min_prep_time * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000286
287 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
288 # .calibrate() method calls each)
289 for i in range(self.calibration_runs):
290 t = timer()
291 for i in calibration_loops:
292 calibrate()
293 t = timer() - t
294 self.overhead_times.append(t / CALIBRATION_LOOPS
295 - min_prep_time)
296
297 # Check the measured times
298 min_overhead = min(self.overhead_times)
299 max_overhead = max(self.overhead_times)
300 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000301 print('Calib. overhead time = %.6fms' % (
302 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000303 if min_overhead < 0.0:
304 raise ValueError('calibration setup did not work')
305 if max_overhead - min_overhead > 0.1:
306 raise ValueError(
307 'overhead calibration timing range too inaccurate: '
308 '%r - %r' % (min_overhead, max_overhead))
309
310 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000311
312 """ Run the test in two phases: first calibrate, then
313 do the actual test. Be careful to keep the calibration
314 timing low w/r to the test timing.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000315
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000316 """
317 test = self.test
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000318 timer = self.get_timer()
319
320 # Get calibration
321 min_overhead = min(self.overhead_times)
322
323 # Test run
324 t = timer()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000325 test()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000326 t = timer() - t
327 if t < MIN_TEST_RUNTIME:
328 raise ValueError('warp factor too high: '
329 'test times are < 10ms')
330 eff_time = t - min_overhead
331 if eff_time < 0:
332 raise ValueError('wrong calibration')
333 self.last_timing = (eff_time, t, min_overhead)
334 self.times.append(eff_time)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000335
336 def calibrate(self):
337
Thomas Wouters477c8d52006-05-27 19:21:47 +0000338 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000339
Thomas Wouters477c8d52006-05-27 19:21:47 +0000340 This method should execute everything that is needed to
341 setup and run the test - except for the actual operations
342 that you intend to measure. pybench uses this method to
343 measure the test implementation overhead.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000344
345 """
346 return
347
348 def test(self):
349
Thomas Wouters477c8d52006-05-27 19:21:47 +0000350 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000351
Thomas Wouters477c8d52006-05-27 19:21:47 +0000352 The test needs to run self.rounds executing
353 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000354
355 """
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000356 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000357
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000358 def stat(self):
359
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000360 """ Return test run statistics as tuple:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000361
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000362 (minimum run time,
363 average run time,
364 total run time,
365 average time per operation,
366 minimum overhead time)
367
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000368 """
369 runs = len(self.times)
370 if runs == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000371 return 0.0, 0.0, 0.0, 0.0
372 min_time = min(self.times)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000373 total_time = sum(self.times)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000374 avg_time = total_time / float(runs)
375 operation_avg = total_time / float(runs
376 * self.rounds
377 * self.operations)
378 if self.overhead_times:
379 min_overhead = min(self.overhead_times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000380 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000381 min_overhead = self.last_timing[2]
382 return min_time, avg_time, total_time, operation_avg, min_overhead
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000383
384### Load Setup
385
386# This has to be done after the definition of the Test class, since
387# the Setup module will import subclasses using this class.
388
389import Setup
390
391### Benchmark base class
392
393class Benchmark:
394
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000395 # Name of the benchmark
396 name = ''
397
398 # Number of benchmark rounds to run
399 rounds = 1
400
401 # Warp factor use to run the tests
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000402 warp = 1 # Warp factor
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000403
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000404 # Average benchmark round time
405 roundtime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000406
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000407 # Benchmark version number as float x.yy
Antoine Pitrou8a681222009-02-07 17:13:31 +0000408 version = 2.1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000409
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000410 # Produce verbose output ?
411 verbose = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000412
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000413 # Dictionary with the machine details
414 machine_details = None
415
416 # Timer used for the benchmark
417 timer = TIMER_PLATFORM_DEFAULT
418
419 def __init__(self, name, verbose=None, timer=None, warp=None,
420 calibration_runs=None):
421
422 if name:
423 self.name = name
Thomas Wouters477c8d52006-05-27 19:21:47 +0000424 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000425 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
426 (time.localtime(time.time())[:6])
427 if verbose is not None:
428 self.verbose = verbose
429 if timer is not None:
430 self.timer = timer
431 if warp is not None:
432 self.warp = warp
433 if calibration_runs is not None:
434 self.calibration_runs = calibration_runs
435
436 # Init vars
437 self.tests = {}
438 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000439 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000440 self.machine_details = get_machine_details()
441
442 # Make .version an instance attribute to have it saved in the
443 # Benchmark pickle
444 self.version = self.version
445
446 def get_timer(self):
447
448 """ Return the timer function to use for the test.
449
450 """
451 return get_timer(self.timer)
452
453 def compatible(self, other):
454
455 """ Return 1/0 depending on whether the benchmark is
456 compatible with the other Benchmark instance or not.
457
458 """
459 if self.version != other.version:
460 return 0
461 if (self.machine_details == other.machine_details and
462 self.timer != other.timer):
463 return 0
464 if (self.calibration_runs == 0 and
465 other.calibration_runs != 0):
466 return 0
467 if (self.calibration_runs != 0 and
468 other.calibration_runs == 0):
469 return 0
470 return 1
471
472 def load_tests(self, setupmod, limitnames=None):
473
474 # Add tests
475 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000476 print('Searching for tests ...')
477 print('--------------------------------------')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000478 for testclass in setupmod.__dict__.values():
479 if not hasattr(testclass, 'is_a_test'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000480 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000481 name = testclass.__name__
Thomas Wouters477c8d52006-05-27 19:21:47 +0000482 if name == 'Test':
483 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000484 if (limitnames is not None and
485 limitnames.search(name) is None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000486 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000487 self.tests[name] = testclass(
488 warp=self.warp,
489 calibration_runs=self.calibration_runs,
490 timer=self.timer)
Guido van Rossum486364b2007-06-30 05:01:58 +0000491 l = sorted(self.tests)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000492 if self.verbose:
493 for name in l:
Guido van Rossum486364b2007-06-30 05:01:58 +0000494 print(' %s' % name)
495 print('--------------------------------------')
496 print(' %i tests found' % len(l))
497 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000498
499 def calibrate(self):
500
Guido van Rossum486364b2007-06-30 05:01:58 +0000501 print('Calibrating tests. Please wait...', end=' ')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000502 sys.stdout.flush()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000503 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000504 print()
505 print()
506 print('Test min max')
507 print('-' * LINE)
508 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000509 for i in range(len(tests)):
510 name, test = tests[i]
511 test.calibrate_test()
512 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000513 print('%30s: %6.3fms %6.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000514 (name,
515 min(test.overhead_times) * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000516 max(test.overhead_times) * MILLI_SECONDS))
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000517 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000518 print()
519 print('Done with the calibration.')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000520 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000521 print('done.')
522 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000523
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000524 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000525
Guido van Rossum486364b2007-06-30 05:01:58 +0000526 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000527 timer = self.get_timer()
Guido van Rossum486364b2007-06-30 05:01:58 +0000528 print('Running %i round(s) of the suite at warp factor %i:' % \
529 (self.rounds, self.warp))
530 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000531 self.roundtimes = []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000532 for i in range(self.rounds):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000533 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000534 print(' Round %-25i effective absolute overhead' % (i+1))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000535 total_eff_time = 0.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000536 for j in range(len(tests)):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000537 name, test = tests[j]
538 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000539 print('%30s:' % name, end=' ')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000540 test.run()
541 (eff_time, abs_time, min_overhead) = test.last_timing
542 total_eff_time = total_eff_time + eff_time
543 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000544 print(' %5.0fms %5.0fms %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000545 (eff_time * MILLI_SECONDS,
546 abs_time * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000547 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000548 self.roundtimes.append(total_eff_time)
549 if self.verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +0000550 print(' '
551 ' ------------------------------')
552 print(' '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000553 ' Totals: %6.0fms' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000554 (total_eff_time * MILLI_SECONDS))
Guido van Rossum486364b2007-06-30 05:01:58 +0000555 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000556 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000557 print('* Round %i done in %.3f seconds.' % (i+1,
558 total_eff_time))
559 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000560
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000561 def stat(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000562
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000563 """ Return benchmark run statistics as tuple:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000564
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000565 (minimum round time,
566 average round time,
567 maximum round time)
568
569 XXX Currently not used, since the benchmark does test
570 statistics across all rounds.
571
572 """
573 runs = len(self.roundtimes)
574 if runs == 0:
575 return 0.0, 0.0
576 min_time = min(self.roundtimes)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000577 total_time = sum(self.roundtimes)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000578 avg_time = total_time / float(runs)
579 max_time = max(self.roundtimes)
580 return (min_time, avg_time, max_time)
581
582 def print_header(self, title='Benchmark'):
583
Guido van Rossum486364b2007-06-30 05:01:58 +0000584 print('-' * LINE)
585 print('%s: %s' % (title, self.name))
586 print('-' * LINE)
587 print()
588 print(' Rounds: %s' % self.rounds)
589 print(' Warp: %s' % self.warp)
590 print(' Timer: %s' % self.timer)
591 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000592 if self.machine_details:
593 print_machine_details(self.machine_details, indent=' ')
Guido van Rossum486364b2007-06-30 05:01:58 +0000594 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000595
596 def print_benchmark(self, hidenoise=0, limitnames=None):
597
Collin Winter6afaeb72007-08-03 17:06:41 +0000598 print('Test '
599 ' minimum average operation overhead')
Guido van Rossum486364b2007-06-30 05:01:58 +0000600 print('-' * LINE)
601 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000602 total_min_time = 0.0
603 total_avg_time = 0.0
604 for name, test in tests:
605 if (limitnames is not None and
606 limitnames.search(name) is None):
607 continue
608 (min_time,
609 avg_time,
610 total_time,
611 op_avg,
612 min_overhead) = test.stat()
613 total_min_time = total_min_time + min_time
614 total_avg_time = total_avg_time + avg_time
Guido van Rossum486364b2007-06-30 05:01:58 +0000615 print('%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000616 (name,
617 min_time * MILLI_SECONDS,
618 avg_time * MILLI_SECONDS,
619 op_avg * MICRO_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000620 min_overhead *MILLI_SECONDS))
621 print('-' * LINE)
Collin Winter6afaeb72007-08-03 17:06:41 +0000622 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000623 ' %6.0fms %6.0fms' %
624 (total_min_time * MILLI_SECONDS,
625 total_avg_time * MILLI_SECONDS,
Collin Winter6afaeb72007-08-03 17:06:41 +0000626 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000627 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000628
629 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
630
631 # Check benchmark versions
632 if compare_to.version != self.version:
Collin Winter6afaeb72007-08-03 17:06:41 +0000633 print('* Benchmark versions differ: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000634 'cannot compare this benchmark to "%s" !' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000635 compare_to.name)
Guido van Rossum486364b2007-06-30 05:01:58 +0000636 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000637 self.print_benchmark(hidenoise=hidenoise,
638 limitnames=limitnames)
639 return
640
641 # Print header
642 compare_to.print_header('Comparing with')
Collin Winter6afaeb72007-08-03 17:06:41 +0000643 print('Test '
644 ' minimum run-time average run-time')
645 print(' '
646 ' this other diff this other diff')
Guido van Rossum486364b2007-06-30 05:01:58 +0000647 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000648
649 # Print test comparisons
Guido van Rossum486364b2007-06-30 05:01:58 +0000650 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000651 total_min_time = other_total_min_time = 0.0
652 total_avg_time = other_total_avg_time = 0.0
653 benchmarks_compatible = self.compatible(compare_to)
654 tests_compatible = 1
655 for name, test in tests:
656 if (limitnames is not None and
657 limitnames.search(name) is None):
658 continue
659 (min_time,
660 avg_time,
661 total_time,
662 op_avg,
663 min_overhead) = test.stat()
664 total_min_time = total_min_time + min_time
665 total_avg_time = total_avg_time + avg_time
666 try:
667 other = compare_to.tests[name]
668 except KeyError:
669 other = None
670 if other is None:
671 # Other benchmark doesn't include the given test
672 min_diff, avg_diff = 'n/a', 'n/a'
673 other_min_time = 0.0
674 other_avg_time = 0.0
675 tests_compatible = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000676 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000677 (other_min_time,
678 other_avg_time,
679 other_total_time,
680 other_op_avg,
681 other_min_overhead) = other.stat()
682 other_total_min_time = other_total_min_time + other_min_time
683 other_total_avg_time = other_total_avg_time + other_avg_time
684 if (benchmarks_compatible and
685 test.compatible(other)):
Ezio Melotti42da6632011-03-15 05:18:48 +0200686 # Both benchmark and tests are comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000687 min_diff = ((min_time * self.warp) /
688 (other_min_time * other.warp) - 1.0)
689 avg_diff = ((avg_time * self.warp) /
690 (other_avg_time * other.warp) - 1.0)
691 if hidenoise and abs(min_diff) < 10.0:
692 min_diff = ''
693 else:
694 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
695 if hidenoise and abs(avg_diff) < 10.0:
696 avg_diff = ''
697 else:
698 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
699 else:
Ezio Melotti42da6632011-03-15 05:18:48 +0200700 # Benchmark or tests are not comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000701 min_diff, avg_diff = 'n/a', 'n/a'
702 tests_compatible = 0
Guido van Rossum486364b2007-06-30 05:01:58 +0000703 print('%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000704 (name,
705 min_time * MILLI_SECONDS,
706 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
707 min_diff,
708 avg_time * MILLI_SECONDS,
709 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
Guido van Rossum486364b2007-06-30 05:01:58 +0000710 avg_diff))
711 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000712
713 # Summarise test results
714 if not benchmarks_compatible or not tests_compatible:
715 min_diff, avg_diff = 'n/a', 'n/a'
716 else:
717 if other_total_min_time != 0.0:
718 min_diff = '%+5.1f%%' % (
719 ((total_min_time * self.warp) /
720 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
721 else:
722 min_diff = 'n/a'
723 if other_total_avg_time != 0.0:
724 avg_diff = '%+5.1f%%' % (
725 ((total_avg_time * self.warp) /
726 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
727 else:
728 avg_diff = 'n/a'
Collin Winter6afaeb72007-08-03 17:06:41 +0000729 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000730 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
731 (total_min_time * MILLI_SECONDS,
732 (other_total_min_time * compare_to.warp/self.warp
733 * MILLI_SECONDS),
734 min_diff,
735 total_avg_time * MILLI_SECONDS,
736 (other_total_avg_time * compare_to.warp/self.warp
737 * MILLI_SECONDS),
738 avg_diff
Collin Winter6afaeb72007-08-03 17:06:41 +0000739 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000740 print()
741 print('(this=%s, other=%s)' % (self.name,
742 compare_to.name))
743 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000744
745class PyBenchCmdline(Application):
746
747 header = ("PYBENCH - a benchmark test suite for Python "
748 "interpreters/compilers.")
749
750 version = __version__
751
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000752 debug = _debug
753
754 options = [ArgumentOption('-n',
755 'number of rounds',
756 Setup.Number_of_rounds),
757 ArgumentOption('-f',
758 'save benchmark to file arg',
759 ''),
760 ArgumentOption('-c',
761 'compare benchmark with the one in file arg',
762 ''),
763 ArgumentOption('-s',
764 'show benchmark in file arg, then exit',
765 ''),
766 ArgumentOption('-w',
767 'set warp factor to arg',
768 Setup.Warp_factor),
769 ArgumentOption('-t',
770 'run only tests with names matching arg',
771 ''),
772 ArgumentOption('-C',
773 'set the number of calibration runs to arg',
774 CALIBRATION_RUNS),
775 SwitchOption('-d',
776 'hide noise in comparisons',
777 0),
778 SwitchOption('-v',
779 'verbose output (not recommended)',
780 0),
781 SwitchOption('--with-gc',
782 'enable garbage collection',
783 0),
784 SwitchOption('--with-syscheck',
785 'use default sys check interval',
786 0),
787 ArgumentOption('--timer',
788 'use given timer',
789 TIMER_PLATFORM_DEFAULT),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000790 ]
791
792 about = """\
793The normal operation is to run the suite and display the
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000794results. Use -f to save them for later reuse or comparisons.
795
796Available timers:
797
798 time.time
799 time.clock
800 systimes.processtime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000801
802Examples:
803
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000804python2.1 pybench.py -f p21.pybench
805python2.5 pybench.py -f p25.pybench
806python pybench.py -s p25.pybench -c p21.pybench
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000807"""
808 copyright = __copyright__
809
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000810 def main(self):
811
812 rounds = self.values['-n']
813 reportfile = self.values['-f']
814 show_bench = self.values['-s']
815 compare_to = self.values['-c']
816 hidenoise = self.values['-d']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000817 warp = int(self.values['-w'])
818 withgc = self.values['--with-gc']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000819 limitnames = self.values['-t']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000820 if limitnames:
821 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000822 print('* limiting test names to one with substring "%s"' % \
823 limitnames)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000824 limitnames = re.compile(limitnames, re.I)
825 else:
826 limitnames = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000827 verbose = self.verbose
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000828 withsyscheck = self.values['--with-syscheck']
829 calibration_runs = self.values['-C']
830 timer = self.values['--timer']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000831
Guido van Rossum486364b2007-06-30 05:01:58 +0000832 print('-' * LINE)
833 print('PYBENCH %s' % __version__)
834 print('-' * LINE)
835 print('* using %s %s' % (
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000836 getattr(platform, 'python_implementation', lambda:'Python')(),
Guido van Rossum486364b2007-06-30 05:01:58 +0000837 ' '.join(sys.version.split())))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000838
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000839 # Switch off garbage collection
840 if not withgc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000841 try:
842 import gc
843 except ImportError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000844 print('* Python version doesn\'t support garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000845 else:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000846 try:
847 gc.disable()
848 except NotImplementedError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000849 print('* Python version doesn\'t support gc.disable')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000850 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000851 print('* disabled garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000852
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000853 # "Disable" sys check interval
854 if not withsyscheck:
855 # Too bad the check interval uses an int instead of a long...
856 value = 2147483647
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000857 try:
858 sys.setcheckinterval(value)
859 except (AttributeError, NotImplementedError):
Guido van Rossum486364b2007-06-30 05:01:58 +0000860 print('* Python version doesn\'t support sys.setcheckinterval')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000861 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000862 print('* system check interval set to maximum: %s' % value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000863
864 if timer == TIMER_SYSTIMES_PROCESSTIME:
865 import systimes
Guido van Rossum486364b2007-06-30 05:01:58 +0000866 print('* using timer: systimes.processtime (%s)' % \
867 systimes.SYSTIMES_IMPLEMENTATION)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000868 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000869 print('* using timer: %s' % timer)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000870
Guido van Rossum486364b2007-06-30 05:01:58 +0000871 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000872
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000873 if compare_to:
874 try:
875 f = open(compare_to,'rb')
876 bench = pickle.load(f)
877 bench.name = compare_to
878 f.close()
879 compare_to = bench
Guido van Rossumb940e112007-01-10 16:19:56 +0000880 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000881 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000882 repr(compare_to),
Guido van Rossum486364b2007-06-30 05:01:58 +0000883 reason))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000884 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000885
886 if show_bench:
887 try:
888 f = open(show_bench,'rb')
889 bench = pickle.load(f)
890 bench.name = show_bench
891 f.close()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000892 bench.print_header()
893 if compare_to:
894 bench.print_comparison(compare_to,
895 hidenoise=hidenoise,
896 limitnames=limitnames)
897 else:
898 bench.print_benchmark(hidenoise=hidenoise,
899 limitnames=limitnames)
Guido van Rossumb940e112007-01-10 16:19:56 +0000900 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000901 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000902 repr(show_bench),
Guido van Rossum486364b2007-06-30 05:01:58 +0000903 reason))
904 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000905 return
906
907 if reportfile:
Guido van Rossum486364b2007-06-30 05:01:58 +0000908 print('Creating benchmark: %s (rounds=%i, warp=%i)' % \
909 (reportfile, rounds, warp))
910 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000911
912 # Create benchmark object
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000913 bench = Benchmark(reportfile,
914 verbose=verbose,
915 timer=timer,
916 warp=warp,
917 calibration_runs=calibration_runs)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000918 bench.rounds = rounds
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000919 bench.load_tests(Setup, limitnames=limitnames)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000920 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000921 bench.calibrate()
922 bench.run()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000923 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000924 print()
925 print('*** KeyboardInterrupt -- Aborting')
926 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000927 return
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000928 bench.print_header()
929 if compare_to:
930 bench.print_comparison(compare_to,
931 hidenoise=hidenoise,
932 limitnames=limitnames)
933 else:
934 bench.print_benchmark(hidenoise=hidenoise,
935 limitnames=limitnames)
936
937 # Ring bell
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000938 sys.stderr.write('\007')
939
940 if reportfile:
941 try:
942 f = open(reportfile,'wb')
943 bench.name = reportfile
944 pickle.dump(bench,f)
945 f.close()
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')
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 %s: %s' % (
Thomas Wouters89f507f2006-12-13 04:49:30 +0000950 reportfile,
Guido van Rossum486364b2007-06-30 05:01:58 +0000951 reason))
952 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000953
954if __name__ == '__main__':
955 PyBenchCmdline()