blob: cac2ddfb852b2f1dbe016d303c0579666f7e1d64 [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'
Victor Stinnerfe98e2f2012-04-29 03:01:20 +020076TIMER_TIME_PROCESS_TIME = 'time.process_time'
77TIMER_TIME_PERF_COUNTER = 'time.perf_counter'
Thomas Wouters0e3f5912006-08-11 14:57:12 +000078TIMER_TIME_CLOCK = 'time.clock'
79TIMER_SYSTIMES_PROCESSTIME = 'systimes.processtime'
80
81# Choose platform default timer
Victor Stinnerfe98e2f2012-04-29 03:01:20 +020082if hasattr(time, 'perf_counter'):
83 TIMER_PLATFORM_DEFAULT = TIMER_TIME_PERF_COUNTER
84elif sys.platform[:3] == 'win':
Thomas Wouters0e3f5912006-08-11 14:57:12 +000085 # On WinXP this has 2.5ms resolution
86 TIMER_PLATFORM_DEFAULT = TIMER_TIME_CLOCK
87else:
88 # On Linux this has 1ms resolution
89 TIMER_PLATFORM_DEFAULT = TIMER_TIME_TIME
90
91# Print debug information ?
92_debug = 0
93
94### Helpers
95
96def get_timer(timertype):
97
98 if timertype == TIMER_TIME_TIME:
99 return time.time
Victor Stinnerfe98e2f2012-04-29 03:01:20 +0200100 elif timertype == TIMER_TIME_PROCESS_TIME:
101 return time.process_time
102 elif timertype == TIMER_TIME_PERF_COUNTER:
103 return time.perf_counter
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000104 elif timertype == TIMER_TIME_CLOCK:
105 return time.clock
106 elif timertype == TIMER_SYSTIMES_PROCESSTIME:
107 import systimes
108 return systimes.processtime
109 else:
110 raise TypeError('unknown timer type: %s' % timertype)
111
112def get_machine_details():
113
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000114 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000115 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000116 buildno, builddate = platform.python_build()
117 python = platform.python_version()
Ezio Melottia9860ae2011-10-04 19:06:00 +0300118 # XXX this is now always UCS4, maybe replace it with 'PEP393' in 3.3+?
Antoine Pitrou251803b2008-07-22 18:03:03 +0000119 if sys.maxunicode == 65535:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000120 # UCS2 build (standard)
Georg Brandlbf82e372008-05-16 17:02:34 +0000121 unitype = 'UCS2'
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000122 else:
123 # UCS4 build (most recent Linux distros)
Georg Brandlbf82e372008-05-16 17:02:34 +0000124 unitype = 'UCS4'
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000125 bits, linkage = platform.architecture()
126 return {
127 'platform': platform.platform(),
128 'processor': platform.processor(),
129 'executable': sys.executable,
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000130 'implementation': getattr(platform, 'python_implementation',
131 lambda:'n/a')(),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000132 'python': platform.python_version(),
133 'compiler': platform.python_compiler(),
134 'buildno': buildno,
135 'builddate': builddate,
Georg Brandlbf82e372008-05-16 17:02:34 +0000136 'unicode': unitype,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000137 'bits': bits,
138 }
139
140def print_machine_details(d, indent=''):
141
142 l = ['Machine Details:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000143 ' Platform ID: %s' % d.get('platform', 'n/a'),
144 ' Processor: %s' % d.get('processor', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000145 '',
146 'Python:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000147 ' Implementation: %s' % d.get('implementation', 'n/a'),
148 ' Executable: %s' % d.get('executable', 'n/a'),
149 ' Version: %s' % d.get('python', 'n/a'),
150 ' Compiler: %s' % d.get('compiler', 'n/a'),
151 ' Bits: %s' % d.get('bits', 'n/a'),
152 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
153 d.get('buildno', 'n/a')),
154 ' Unicode: %s' % d.get('unicode', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000155 ]
Guido van Rossum486364b2007-06-30 05:01:58 +0000156 joiner = '\n' + indent
157 print(indent + joiner.join(l) + '\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000158
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000159### Test baseclass
160
161class Test:
162
163 """ All test must have this class as baseclass. It provides
164 the necessary interface to the benchmark machinery.
165
166 The tests must set .rounds to a value high enough to let the
167 test run between 20-50 seconds. This is needed because
168 clock()-timing only gives rather inaccurate values (on Linux,
169 for example, it is accurate to a few hundreths of a
170 second). If you don't want to wait that long, use a warp
171 factor larger than 1.
172
173 It is also important to set the .operations variable to a
174 value representing the number of "virtual operations" done per
175 call of .run().
176
177 If you change a test in some way, don't forget to increase
Guido van Rossumd8faa362007-04-27 19:54:29 +0000178 its version number.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000179
180 """
181
182 ### Instance variables that each test should override
183
184 # Version number of the test as float (x.yy); this is important
185 # for comparisons of benchmark runs - tests with unequal version
186 # number will not get compared.
Antoine Pitrou8a681222009-02-07 17:13:31 +0000187 version = 2.1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000188
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000189 # The number of abstract operations done in each round of the
190 # test. An operation is the basic unit of what you want to
191 # measure. The benchmark will output the amount of run-time per
192 # operation. Note that in order to raise the measured timings
193 # significantly above noise level, it is often required to repeat
194 # sets of operations more than once per test round. The measured
195 # overhead per test round should be less than 1 second.
196 operations = 1
197
198 # Number of rounds to execute per test run. This should be
199 # adjusted to a figure that results in a test run-time of between
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000200 # 1-2 seconds.
201 rounds = 100000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000202
203 ### Internal variables
204
205 # Mark this class as implementing a test
206 is_a_test = 1
207
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000208 # Last timing: (real, run, overhead)
209 last_timing = (0.0, 0.0, 0.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000210
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000211 # Warp factor to use for this test
212 warp = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000213
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000214 # Number of calibration runs to use
215 calibration_runs = CALIBRATION_RUNS
216
217 # List of calibration timings
218 overhead_times = None
219
220 # List of test run timings
221 times = []
222
223 # Timer used for the benchmark
224 timer = TIMER_PLATFORM_DEFAULT
225
226 def __init__(self, warp=None, calibration_runs=None, timer=None):
227
228 # Set parameters
229 if warp is not None:
230 self.rounds = int(self.rounds / warp)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000231 if self.rounds == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000232 raise ValueError('warp factor set too high')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000233 self.warp = warp
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000234 if calibration_runs is not None:
235 if (not ALLOW_SKIPPING_CALIBRATION and
236 calibration_runs < 1):
237 raise ValueError('at least one calibration run is required')
238 self.calibration_runs = calibration_runs
239 if timer is not None:
Benjamin Petersonf6489f92009-11-25 17:46:26 +0000240 self.timer = timer
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000241
242 # Init variables
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000243 self.times = []
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000244 self.overhead_times = []
245
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000246 # We want these to be in the instance dict, so that pickle
247 # saves them
248 self.version = self.version
249 self.operations = self.operations
250 self.rounds = self.rounds
251
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000252 def get_timer(self):
253
254 """ Return the timer function to use for the test.
255
256 """
257 return get_timer(self.timer)
258
259 def compatible(self, other):
260
261 """ Return 1/0 depending on whether the test is compatible
262 with the other Test instance or not.
263
264 """
265 if self.version != other.version:
266 return 0
267 if self.rounds != other.rounds:
268 return 0
269 return 1
270
271 def calibrate_test(self):
272
273 if self.calibration_runs == 0:
274 self.overhead_times = [0.0]
275 return
276
277 calibrate = self.calibrate
278 timer = self.get_timer()
279 calibration_loops = range(CALIBRATION_LOOPS)
280
281 # Time the calibration loop overhead
282 prep_times = []
283 for i in range(self.calibration_runs):
284 t = timer()
285 for i in calibration_loops:
286 pass
287 t = timer() - t
Jesus Cea8f14bbd2011-04-25 03:24:08 +0200288 prep_times.append(t / CALIBRATION_LOOPS)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000289 min_prep_time = min(prep_times)
290 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000291 print()
292 print('Calib. prep time = %.6fms' % (
293 min_prep_time * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000294
295 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
296 # .calibrate() method calls each)
297 for i in range(self.calibration_runs):
298 t = timer()
299 for i in calibration_loops:
300 calibrate()
301 t = timer() - t
302 self.overhead_times.append(t / CALIBRATION_LOOPS
303 - min_prep_time)
304
305 # Check the measured times
306 min_overhead = min(self.overhead_times)
307 max_overhead = max(self.overhead_times)
308 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000309 print('Calib. overhead time = %.6fms' % (
310 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000311 if min_overhead < 0.0:
312 raise ValueError('calibration setup did not work')
313 if max_overhead - min_overhead > 0.1:
314 raise ValueError(
315 'overhead calibration timing range too inaccurate: '
316 '%r - %r' % (min_overhead, max_overhead))
317
318 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000319
320 """ Run the test in two phases: first calibrate, then
321 do the actual test. Be careful to keep the calibration
322 timing low w/r to the test timing.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000323
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000324 """
325 test = self.test
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000326 timer = self.get_timer()
327
328 # Get calibration
329 min_overhead = min(self.overhead_times)
330
331 # Test run
332 t = timer()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000333 test()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000334 t = timer() - t
335 if t < MIN_TEST_RUNTIME:
336 raise ValueError('warp factor too high: '
337 'test times are < 10ms')
338 eff_time = t - min_overhead
339 if eff_time < 0:
340 raise ValueError('wrong calibration')
341 self.last_timing = (eff_time, t, min_overhead)
342 self.times.append(eff_time)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000343
344 def calibrate(self):
345
Thomas Wouters477c8d52006-05-27 19:21:47 +0000346 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000347
Thomas Wouters477c8d52006-05-27 19:21:47 +0000348 This method should execute everything that is needed to
349 setup and run the test - except for the actual operations
350 that you intend to measure. pybench uses this method to
351 measure the test implementation overhead.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000352
353 """
354 return
355
356 def test(self):
357
Thomas Wouters477c8d52006-05-27 19:21:47 +0000358 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000359
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360 The test needs to run self.rounds executing
361 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000362
363 """
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000364 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000365
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000366 def stat(self):
367
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000368 """ Return test run statistics as tuple:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000369
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000370 (minimum run time,
371 average run time,
372 total run time,
373 average time per operation,
374 minimum overhead time)
375
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000376 """
377 runs = len(self.times)
378 if runs == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000379 return 0.0, 0.0, 0.0, 0.0
380 min_time = min(self.times)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000381 total_time = sum(self.times)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000382 avg_time = total_time / float(runs)
383 operation_avg = total_time / float(runs
384 * self.rounds
385 * self.operations)
386 if self.overhead_times:
387 min_overhead = min(self.overhead_times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000388 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000389 min_overhead = self.last_timing[2]
390 return min_time, avg_time, total_time, operation_avg, min_overhead
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000391
392### Load Setup
393
394# This has to be done after the definition of the Test class, since
395# the Setup module will import subclasses using this class.
396
397import Setup
398
399### Benchmark base class
400
401class Benchmark:
402
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000403 # Name of the benchmark
404 name = ''
405
406 # Number of benchmark rounds to run
407 rounds = 1
408
409 # Warp factor use to run the tests
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000410 warp = 1 # Warp factor
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000411
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000412 # Average benchmark round time
413 roundtime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000414
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000415 # Benchmark version number as float x.yy
Antoine Pitrou8a681222009-02-07 17:13:31 +0000416 version = 2.1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000417
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000418 # Produce verbose output ?
419 verbose = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000420
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000421 # Dictionary with the machine details
422 machine_details = None
423
424 # Timer used for the benchmark
425 timer = TIMER_PLATFORM_DEFAULT
426
427 def __init__(self, name, verbose=None, timer=None, warp=None,
428 calibration_runs=None):
429
430 if name:
431 self.name = name
Thomas Wouters477c8d52006-05-27 19:21:47 +0000432 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000433 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
434 (time.localtime(time.time())[:6])
435 if verbose is not None:
436 self.verbose = verbose
437 if timer is not None:
438 self.timer = timer
439 if warp is not None:
440 self.warp = warp
441 if calibration_runs is not None:
442 self.calibration_runs = calibration_runs
443
444 # Init vars
445 self.tests = {}
446 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000447 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000448 self.machine_details = get_machine_details()
449
450 # Make .version an instance attribute to have it saved in the
451 # Benchmark pickle
452 self.version = self.version
453
454 def get_timer(self):
455
456 """ Return the timer function to use for the test.
457
458 """
459 return get_timer(self.timer)
460
461 def compatible(self, other):
462
463 """ Return 1/0 depending on whether the benchmark is
464 compatible with the other Benchmark instance or not.
465
466 """
467 if self.version != other.version:
468 return 0
469 if (self.machine_details == other.machine_details and
470 self.timer != other.timer):
471 return 0
472 if (self.calibration_runs == 0 and
473 other.calibration_runs != 0):
474 return 0
475 if (self.calibration_runs != 0 and
476 other.calibration_runs == 0):
477 return 0
478 return 1
479
480 def load_tests(self, setupmod, limitnames=None):
481
482 # Add tests
483 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000484 print('Searching for tests ...')
485 print('--------------------------------------')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000486 for testclass in setupmod.__dict__.values():
487 if not hasattr(testclass, 'is_a_test'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000488 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000489 name = testclass.__name__
Thomas Wouters477c8d52006-05-27 19:21:47 +0000490 if name == 'Test':
491 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000492 if (limitnames is not None and
493 limitnames.search(name) is None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000494 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000495 self.tests[name] = testclass(
496 warp=self.warp,
497 calibration_runs=self.calibration_runs,
498 timer=self.timer)
Guido van Rossum486364b2007-06-30 05:01:58 +0000499 l = sorted(self.tests)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000500 if self.verbose:
501 for name in l:
Guido van Rossum486364b2007-06-30 05:01:58 +0000502 print(' %s' % name)
503 print('--------------------------------------')
504 print(' %i tests found' % len(l))
505 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000506
507 def calibrate(self):
508
Guido van Rossum486364b2007-06-30 05:01:58 +0000509 print('Calibrating tests. Please wait...', end=' ')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000510 sys.stdout.flush()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000511 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000512 print()
513 print()
514 print('Test min max')
515 print('-' * LINE)
516 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000517 for i in range(len(tests)):
518 name, test = tests[i]
519 test.calibrate_test()
520 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000521 print('%30s: %6.3fms %6.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000522 (name,
523 min(test.overhead_times) * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000524 max(test.overhead_times) * MILLI_SECONDS))
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000525 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000526 print()
527 print('Done with the calibration.')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000528 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000529 print('done.')
530 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000531
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000532 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000533
Guido van Rossum486364b2007-06-30 05:01:58 +0000534 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000535 timer = self.get_timer()
Guido van Rossum486364b2007-06-30 05:01:58 +0000536 print('Running %i round(s) of the suite at warp factor %i:' % \
537 (self.rounds, self.warp))
538 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000539 self.roundtimes = []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000540 for i in range(self.rounds):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000541 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000542 print(' Round %-25i effective absolute overhead' % (i+1))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000543 total_eff_time = 0.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000544 for j in range(len(tests)):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000545 name, test = tests[j]
546 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000547 print('%30s:' % name, end=' ')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000548 test.run()
549 (eff_time, abs_time, min_overhead) = test.last_timing
550 total_eff_time = total_eff_time + eff_time
551 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000552 print(' %5.0fms %5.0fms %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000553 (eff_time * MILLI_SECONDS,
554 abs_time * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000555 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000556 self.roundtimes.append(total_eff_time)
557 if self.verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +0000558 print(' '
559 ' ------------------------------')
560 print(' '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000561 ' Totals: %6.0fms' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000562 (total_eff_time * MILLI_SECONDS))
Guido van Rossum486364b2007-06-30 05:01:58 +0000563 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000564 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000565 print('* Round %i done in %.3f seconds.' % (i+1,
566 total_eff_time))
567 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000568
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000569 def stat(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000570
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000571 """ Return benchmark run statistics as tuple:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000572
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000573 (minimum round time,
574 average round time,
575 maximum round time)
576
577 XXX Currently not used, since the benchmark does test
578 statistics across all rounds.
579
580 """
581 runs = len(self.roundtimes)
582 if runs == 0:
583 return 0.0, 0.0
584 min_time = min(self.roundtimes)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000585 total_time = sum(self.roundtimes)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000586 avg_time = total_time / float(runs)
587 max_time = max(self.roundtimes)
588 return (min_time, avg_time, max_time)
589
590 def print_header(self, title='Benchmark'):
591
Guido van Rossum486364b2007-06-30 05:01:58 +0000592 print('-' * LINE)
593 print('%s: %s' % (title, self.name))
594 print('-' * LINE)
595 print()
596 print(' Rounds: %s' % self.rounds)
597 print(' Warp: %s' % self.warp)
598 print(' Timer: %s' % self.timer)
599 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000600 if self.machine_details:
601 print_machine_details(self.machine_details, indent=' ')
Guido van Rossum486364b2007-06-30 05:01:58 +0000602 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000603
604 def print_benchmark(self, hidenoise=0, limitnames=None):
605
Collin Winter6afaeb72007-08-03 17:06:41 +0000606 print('Test '
607 ' minimum average operation overhead')
Guido van Rossum486364b2007-06-30 05:01:58 +0000608 print('-' * LINE)
609 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000610 total_min_time = 0.0
611 total_avg_time = 0.0
612 for name, test in tests:
613 if (limitnames is not None and
614 limitnames.search(name) is None):
615 continue
616 (min_time,
617 avg_time,
618 total_time,
619 op_avg,
620 min_overhead) = test.stat()
621 total_min_time = total_min_time + min_time
622 total_avg_time = total_avg_time + avg_time
Guido van Rossum486364b2007-06-30 05:01:58 +0000623 print('%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000624 (name,
625 min_time * MILLI_SECONDS,
626 avg_time * MILLI_SECONDS,
627 op_avg * MICRO_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000628 min_overhead *MILLI_SECONDS))
629 print('-' * LINE)
Collin Winter6afaeb72007-08-03 17:06:41 +0000630 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000631 ' %6.0fms %6.0fms' %
632 (total_min_time * MILLI_SECONDS,
633 total_avg_time * MILLI_SECONDS,
Collin Winter6afaeb72007-08-03 17:06:41 +0000634 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000635 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000636
637 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
638
639 # Check benchmark versions
640 if compare_to.version != self.version:
Collin Winter6afaeb72007-08-03 17:06:41 +0000641 print('* Benchmark versions differ: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000642 'cannot compare this benchmark to "%s" !' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000643 compare_to.name)
Guido van Rossum486364b2007-06-30 05:01:58 +0000644 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000645 self.print_benchmark(hidenoise=hidenoise,
646 limitnames=limitnames)
647 return
648
649 # Print header
650 compare_to.print_header('Comparing with')
Collin Winter6afaeb72007-08-03 17:06:41 +0000651 print('Test '
652 ' minimum run-time average run-time')
653 print(' '
654 ' this other diff this other diff')
Guido van Rossum486364b2007-06-30 05:01:58 +0000655 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000656
657 # Print test comparisons
Guido van Rossum486364b2007-06-30 05:01:58 +0000658 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000659 total_min_time = other_total_min_time = 0.0
660 total_avg_time = other_total_avg_time = 0.0
661 benchmarks_compatible = self.compatible(compare_to)
662 tests_compatible = 1
663 for name, test in tests:
664 if (limitnames is not None and
665 limitnames.search(name) is None):
666 continue
667 (min_time,
668 avg_time,
669 total_time,
670 op_avg,
671 min_overhead) = test.stat()
672 total_min_time = total_min_time + min_time
673 total_avg_time = total_avg_time + avg_time
674 try:
675 other = compare_to.tests[name]
676 except KeyError:
677 other = None
678 if other is None:
679 # Other benchmark doesn't include the given test
680 min_diff, avg_diff = 'n/a', 'n/a'
681 other_min_time = 0.0
682 other_avg_time = 0.0
683 tests_compatible = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000684 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000685 (other_min_time,
686 other_avg_time,
687 other_total_time,
688 other_op_avg,
689 other_min_overhead) = other.stat()
690 other_total_min_time = other_total_min_time + other_min_time
691 other_total_avg_time = other_total_avg_time + other_avg_time
692 if (benchmarks_compatible and
693 test.compatible(other)):
Ezio Melotti42da6632011-03-15 05:18:48 +0200694 # Both benchmark and tests are comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000695 min_diff = ((min_time * self.warp) /
696 (other_min_time * other.warp) - 1.0)
697 avg_diff = ((avg_time * self.warp) /
698 (other_avg_time * other.warp) - 1.0)
699 if hidenoise and abs(min_diff) < 10.0:
700 min_diff = ''
701 else:
702 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
703 if hidenoise and abs(avg_diff) < 10.0:
704 avg_diff = ''
705 else:
706 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
707 else:
Ezio Melotti42da6632011-03-15 05:18:48 +0200708 # Benchmark or tests are not comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000709 min_diff, avg_diff = 'n/a', 'n/a'
710 tests_compatible = 0
Guido van Rossum486364b2007-06-30 05:01:58 +0000711 print('%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000712 (name,
713 min_time * MILLI_SECONDS,
714 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
715 min_diff,
716 avg_time * MILLI_SECONDS,
717 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
Guido van Rossum486364b2007-06-30 05:01:58 +0000718 avg_diff))
719 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000720
721 # Summarise test results
722 if not benchmarks_compatible or not tests_compatible:
723 min_diff, avg_diff = 'n/a', 'n/a'
724 else:
725 if other_total_min_time != 0.0:
726 min_diff = '%+5.1f%%' % (
727 ((total_min_time * self.warp) /
728 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
729 else:
730 min_diff = 'n/a'
731 if other_total_avg_time != 0.0:
732 avg_diff = '%+5.1f%%' % (
733 ((total_avg_time * self.warp) /
734 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
735 else:
736 avg_diff = 'n/a'
Collin Winter6afaeb72007-08-03 17:06:41 +0000737 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000738 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
739 (total_min_time * MILLI_SECONDS,
740 (other_total_min_time * compare_to.warp/self.warp
741 * MILLI_SECONDS),
742 min_diff,
743 total_avg_time * MILLI_SECONDS,
744 (other_total_avg_time * compare_to.warp/self.warp
745 * MILLI_SECONDS),
746 avg_diff
Collin Winter6afaeb72007-08-03 17:06:41 +0000747 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000748 print()
749 print('(this=%s, other=%s)' % (self.name,
750 compare_to.name))
751 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000752
753class PyBenchCmdline(Application):
754
755 header = ("PYBENCH - a benchmark test suite for Python "
756 "interpreters/compilers.")
757
758 version = __version__
759
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000760 debug = _debug
761
762 options = [ArgumentOption('-n',
763 'number of rounds',
764 Setup.Number_of_rounds),
765 ArgumentOption('-f',
766 'save benchmark to file arg',
767 ''),
768 ArgumentOption('-c',
769 'compare benchmark with the one in file arg',
770 ''),
771 ArgumentOption('-s',
772 'show benchmark in file arg, then exit',
773 ''),
774 ArgumentOption('-w',
775 'set warp factor to arg',
776 Setup.Warp_factor),
777 ArgumentOption('-t',
778 'run only tests with names matching arg',
779 ''),
780 ArgumentOption('-C',
781 'set the number of calibration runs to arg',
782 CALIBRATION_RUNS),
783 SwitchOption('-d',
784 'hide noise in comparisons',
785 0),
786 SwitchOption('-v',
787 'verbose output (not recommended)',
788 0),
789 SwitchOption('--with-gc',
790 'enable garbage collection',
791 0),
792 SwitchOption('--with-syscheck',
793 'use default sys check interval',
794 0),
795 ArgumentOption('--timer',
796 'use given timer',
797 TIMER_PLATFORM_DEFAULT),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000798 ]
799
800 about = """\
801The normal operation is to run the suite and display the
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000802results. Use -f to save them for later reuse or comparisons.
803
804Available timers:
805
806 time.time
807 time.clock
808 systimes.processtime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000809
810Examples:
811
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000812python2.1 pybench.py -f p21.pybench
813python2.5 pybench.py -f p25.pybench
814python pybench.py -s p25.pybench -c p21.pybench
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000815"""
816 copyright = __copyright__
817
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000818 def main(self):
819
820 rounds = self.values['-n']
821 reportfile = self.values['-f']
822 show_bench = self.values['-s']
823 compare_to = self.values['-c']
824 hidenoise = self.values['-d']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000825 warp = int(self.values['-w'])
826 withgc = self.values['--with-gc']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000827 limitnames = self.values['-t']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000828 if limitnames:
829 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000830 print('* limiting test names to one with substring "%s"' % \
831 limitnames)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000832 limitnames = re.compile(limitnames, re.I)
833 else:
834 limitnames = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000835 verbose = self.verbose
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000836 withsyscheck = self.values['--with-syscheck']
837 calibration_runs = self.values['-C']
838 timer = self.values['--timer']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000839
Guido van Rossum486364b2007-06-30 05:01:58 +0000840 print('-' * LINE)
841 print('PYBENCH %s' % __version__)
842 print('-' * LINE)
843 print('* using %s %s' % (
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000844 getattr(platform, 'python_implementation', lambda:'Python')(),
Guido van Rossum486364b2007-06-30 05:01:58 +0000845 ' '.join(sys.version.split())))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000846
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000847 # Switch off garbage collection
848 if not withgc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000849 try:
850 import gc
851 except ImportError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000852 print('* Python version doesn\'t support garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000853 else:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000854 try:
855 gc.disable()
856 except NotImplementedError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000857 print('* Python version doesn\'t support gc.disable')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000858 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000859 print('* disabled garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000860
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000861 # "Disable" sys check interval
862 if not withsyscheck:
863 # Too bad the check interval uses an int instead of a long...
864 value = 2147483647
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000865 try:
866 sys.setcheckinterval(value)
867 except (AttributeError, NotImplementedError):
Guido van Rossum486364b2007-06-30 05:01:58 +0000868 print('* Python version doesn\'t support sys.setcheckinterval')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000869 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000870 print('* system check interval set to maximum: %s' % value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000871
872 if timer == TIMER_SYSTIMES_PROCESSTIME:
873 import systimes
Guido van Rossum486364b2007-06-30 05:01:58 +0000874 print('* using timer: systimes.processtime (%s)' % \
875 systimes.SYSTIMES_IMPLEMENTATION)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000876 else:
Victor Stinnerfe98e2f2012-04-29 03:01:20 +0200877 # Check that the clock function does exist
878 try:
879 get_timer(timer)
880 except TypeError:
881 print("* Error: Unknown timer: %s" % timer)
882 return
883
Guido van Rossum486364b2007-06-30 05:01:58 +0000884 print('* using timer: %s' % timer)
Victor Stinnerfe98e2f2012-04-29 03:01:20 +0200885 if hasattr(time, 'get_clock_info'):
886 info = time.get_clock_info(timer[5:])
887 print('* timer: resolution=%s, implementation=%s'
888 % (info.resolution, info.implementation))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000889
Guido van Rossum486364b2007-06-30 05:01:58 +0000890 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000891
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000892 if compare_to:
893 try:
894 f = open(compare_to,'rb')
895 bench = pickle.load(f)
896 bench.name = compare_to
897 f.close()
898 compare_to = bench
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(compare_to),
Guido van Rossum486364b2007-06-30 05:01:58 +0000902 reason))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000903 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000904
905 if show_bench:
906 try:
907 f = open(show_bench,'rb')
908 bench = pickle.load(f)
909 bench.name = show_bench
910 f.close()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000911 bench.print_header()
912 if compare_to:
913 bench.print_comparison(compare_to,
914 hidenoise=hidenoise,
915 limitnames=limitnames)
916 else:
917 bench.print_benchmark(hidenoise=hidenoise,
918 limitnames=limitnames)
Guido van Rossumb940e112007-01-10 16:19:56 +0000919 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000920 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000921 repr(show_bench),
Guido van Rossum486364b2007-06-30 05:01:58 +0000922 reason))
923 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000924 return
925
926 if reportfile:
Guido van Rossum486364b2007-06-30 05:01:58 +0000927 print('Creating benchmark: %s (rounds=%i, warp=%i)' % \
928 (reportfile, rounds, warp))
929 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000930
931 # Create benchmark object
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000932 bench = Benchmark(reportfile,
933 verbose=verbose,
934 timer=timer,
935 warp=warp,
936 calibration_runs=calibration_runs)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000937 bench.rounds = rounds
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000938 bench.load_tests(Setup, limitnames=limitnames)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000939 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000940 bench.calibrate()
941 bench.run()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000942 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000943 print()
944 print('*** KeyboardInterrupt -- Aborting')
945 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000946 return
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000947 bench.print_header()
948 if compare_to:
949 bench.print_comparison(compare_to,
950 hidenoise=hidenoise,
951 limitnames=limitnames)
952 else:
953 bench.print_benchmark(hidenoise=hidenoise,
954 limitnames=limitnames)
955
956 # Ring bell
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000957 sys.stderr.write('\007')
958
959 if reportfile:
960 try:
961 f = open(reportfile,'wb')
962 bench.name = reportfile
963 pickle.dump(bench,f)
964 f.close()
Guido van Rossumb940e112007-01-10 16:19:56 +0000965 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000966 print('* Error opening/writing reportfile')
Guido van Rossumb940e112007-01-10 16:19:56 +0000967 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000968 print('* Error opening/writing reportfile %s: %s' % (
Thomas Wouters89f507f2006-12-13 04:49:30 +0000969 reportfile,
Guido van Rossum486364b2007-06-30 05:01:58 +0000970 reason))
971 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000972
973if __name__ == '__main__':
974 PyBenchCmdline()