blob: 942f56d2926de9dda37b14199e2e799c78f7c2a4 [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
Florent Xiclunac2074012012-07-07 17:03:54 +020038import sys
39import time
40import platform
Thomas Wouters49fd7fa2006-04-21 10:40:58 +000041from CommandLine import *
42
43try:
44 import cPickle
45 pickle = cPickle
46except ImportError:
47 import pickle
48
Thomas Wouters0e3f5912006-08-11 14:57:12 +000049# Version number; version history: see README file !
Antoine Pitrou8a681222009-02-07 17:13:31 +000050__version__ = '2.1'
Thomas Wouters0e3f5912006-08-11 14:57:12 +000051
52### Constants
53
54# Second fractions
55MILLI_SECONDS = 1e3
56MICRO_SECONDS = 1e6
57
58# Percent unit
59PERCENT = 100
60
61# Horizontal line length
62LINE = 79
63
64# Minimum test run-time
65MIN_TEST_RUNTIME = 1e-3
66
67# Number of calibration runs to use for calibrating the tests
68CALIBRATION_RUNS = 20
69
70# Number of calibration loops to run for each calibration run
71CALIBRATION_LOOPS = 20
72
73# Allow skipping calibration ?
74ALLOW_SKIPPING_CALIBRATION = 1
75
76# Timer types
77TIMER_TIME_TIME = 'time.time'
Victor Stinnerfe98e2f2012-04-29 03:01:20 +020078TIMER_TIME_PROCESS_TIME = 'time.process_time'
79TIMER_TIME_PERF_COUNTER = 'time.perf_counter'
Thomas Wouters0e3f5912006-08-11 14:57:12 +000080TIMER_TIME_CLOCK = 'time.clock'
81TIMER_SYSTIMES_PROCESSTIME = 'systimes.processtime'
82
83# Choose platform default timer
Victor Stinnerfe98e2f2012-04-29 03:01:20 +020084if hasattr(time, 'perf_counter'):
85 TIMER_PLATFORM_DEFAULT = TIMER_TIME_PERF_COUNTER
86elif sys.platform[:3] == 'win':
Thomas Wouters0e3f5912006-08-11 14:57:12 +000087 # On WinXP this has 2.5ms resolution
88 TIMER_PLATFORM_DEFAULT = TIMER_TIME_CLOCK
89else:
90 # On Linux this has 1ms resolution
91 TIMER_PLATFORM_DEFAULT = TIMER_TIME_TIME
92
93# Print debug information ?
94_debug = 0
95
96### Helpers
97
98def get_timer(timertype):
99
100 if timertype == TIMER_TIME_TIME:
101 return time.time
Victor Stinnerfe98e2f2012-04-29 03:01:20 +0200102 elif timertype == TIMER_TIME_PROCESS_TIME:
103 return time.process_time
104 elif timertype == TIMER_TIME_PERF_COUNTER:
105 return time.perf_counter
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000106 elif timertype == TIMER_TIME_CLOCK:
107 return time.clock
108 elif timertype == TIMER_SYSTIMES_PROCESSTIME:
109 import systimes
110 return systimes.processtime
111 else:
112 raise TypeError('unknown timer type: %s' % timertype)
113
114def get_machine_details():
115
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000116 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000117 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000118 buildno, builddate = platform.python_build()
119 python = platform.python_version()
Ezio Melottia9860ae2011-10-04 19:06:00 +0300120 # XXX this is now always UCS4, maybe replace it with 'PEP393' in 3.3+?
Antoine Pitrou251803b2008-07-22 18:03:03 +0000121 if sys.maxunicode == 65535:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000122 # UCS2 build (standard)
Georg Brandlbf82e372008-05-16 17:02:34 +0000123 unitype = 'UCS2'
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000124 else:
125 # UCS4 build (most recent Linux distros)
Georg Brandlbf82e372008-05-16 17:02:34 +0000126 unitype = 'UCS4'
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000127 bits, linkage = platform.architecture()
128 return {
129 'platform': platform.platform(),
130 'processor': platform.processor(),
131 'executable': sys.executable,
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000132 'implementation': getattr(platform, 'python_implementation',
133 lambda:'n/a')(),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000134 'python': platform.python_version(),
135 'compiler': platform.python_compiler(),
136 'buildno': buildno,
137 'builddate': builddate,
Georg Brandlbf82e372008-05-16 17:02:34 +0000138 'unicode': unitype,
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000139 'bits': bits,
140 }
141
142def print_machine_details(d, indent=''):
143
144 l = ['Machine Details:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000145 ' Platform ID: %s' % d.get('platform', 'n/a'),
146 ' Processor: %s' % d.get('processor', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000147 '',
148 'Python:',
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000149 ' Implementation: %s' % d.get('implementation', 'n/a'),
150 ' Executable: %s' % d.get('executable', 'n/a'),
151 ' Version: %s' % d.get('python', 'n/a'),
152 ' Compiler: %s' % d.get('compiler', 'n/a'),
153 ' Bits: %s' % d.get('bits', 'n/a'),
154 ' Build: %s (#%s)' % (d.get('builddate', 'n/a'),
155 d.get('buildno', 'n/a')),
156 ' Unicode: %s' % d.get('unicode', 'n/a'),
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000157 ]
Guido van Rossum486364b2007-06-30 05:01:58 +0000158 joiner = '\n' + indent
159 print(indent + joiner.join(l) + '\n')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000160
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000161### Test baseclass
162
163class Test:
164
165 """ All test must have this class as baseclass. It provides
166 the necessary interface to the benchmark machinery.
167
168 The tests must set .rounds to a value high enough to let the
169 test run between 20-50 seconds. This is needed because
170 clock()-timing only gives rather inaccurate values (on Linux,
171 for example, it is accurate to a few hundreths of a
172 second). If you don't want to wait that long, use a warp
173 factor larger than 1.
174
175 It is also important to set the .operations variable to a
176 value representing the number of "virtual operations" done per
177 call of .run().
178
179 If you change a test in some way, don't forget to increase
Guido van Rossumd8faa362007-04-27 19:54:29 +0000180 its version number.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000181
182 """
183
184 ### Instance variables that each test should override
185
186 # Version number of the test as float (x.yy); this is important
187 # for comparisons of benchmark runs - tests with unequal version
188 # number will not get compared.
Antoine Pitrou8a681222009-02-07 17:13:31 +0000189 version = 2.1
Thomas Wouters477c8d52006-05-27 19:21:47 +0000190
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000191 # The number of abstract operations done in each round of the
192 # test. An operation is the basic unit of what you want to
193 # measure. The benchmark will output the amount of run-time per
194 # operation. Note that in order to raise the measured timings
195 # significantly above noise level, it is often required to repeat
196 # sets of operations more than once per test round. The measured
197 # overhead per test round should be less than 1 second.
198 operations = 1
199
200 # Number of rounds to execute per test run. This should be
201 # adjusted to a figure that results in a test run-time of between
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000202 # 1-2 seconds.
203 rounds = 100000
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000204
205 ### Internal variables
206
207 # Mark this class as implementing a test
208 is_a_test = 1
209
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000210 # Last timing: (real, run, overhead)
211 last_timing = (0.0, 0.0, 0.0)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000212
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000213 # Warp factor to use for this test
214 warp = 1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000215
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000216 # Number of calibration runs to use
217 calibration_runs = CALIBRATION_RUNS
218
219 # List of calibration timings
220 overhead_times = None
221
222 # List of test run timings
223 times = []
224
225 # Timer used for the benchmark
226 timer = TIMER_PLATFORM_DEFAULT
227
228 def __init__(self, warp=None, calibration_runs=None, timer=None):
229
230 # Set parameters
231 if warp is not None:
232 self.rounds = int(self.rounds / warp)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000233 if self.rounds == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000234 raise ValueError('warp factor set too high')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000235 self.warp = warp
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000236 if calibration_runs is not None:
237 if (not ALLOW_SKIPPING_CALIBRATION and
238 calibration_runs < 1):
239 raise ValueError('at least one calibration run is required')
240 self.calibration_runs = calibration_runs
241 if timer is not None:
Benjamin Petersonf6489f92009-11-25 17:46:26 +0000242 self.timer = timer
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000243
244 # Init variables
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000245 self.times = []
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000246 self.overhead_times = []
247
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000248 # We want these to be in the instance dict, so that pickle
249 # saves them
250 self.version = self.version
251 self.operations = self.operations
252 self.rounds = self.rounds
253
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000254 def get_timer(self):
255
256 """ Return the timer function to use for the test.
257
258 """
259 return get_timer(self.timer)
260
261 def compatible(self, other):
262
263 """ Return 1/0 depending on whether the test is compatible
264 with the other Test instance or not.
265
266 """
267 if self.version != other.version:
268 return 0
269 if self.rounds != other.rounds:
270 return 0
271 return 1
272
273 def calibrate_test(self):
274
275 if self.calibration_runs == 0:
276 self.overhead_times = [0.0]
277 return
278
279 calibrate = self.calibrate
280 timer = self.get_timer()
281 calibration_loops = range(CALIBRATION_LOOPS)
282
283 # Time the calibration loop overhead
284 prep_times = []
285 for i in range(self.calibration_runs):
286 t = timer()
287 for i in calibration_loops:
288 pass
289 t = timer() - t
Jesus Cea8f14bbd2011-04-25 03:24:08 +0200290 prep_times.append(t / CALIBRATION_LOOPS)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000291 min_prep_time = min(prep_times)
292 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000293 print()
294 print('Calib. prep time = %.6fms' % (
295 min_prep_time * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000296
297 # Time the calibration runs (doing CALIBRATION_LOOPS loops of
298 # .calibrate() method calls each)
299 for i in range(self.calibration_runs):
300 t = timer()
301 for i in calibration_loops:
302 calibrate()
303 t = timer() - t
304 self.overhead_times.append(t / CALIBRATION_LOOPS
305 - min_prep_time)
306
307 # Check the measured times
308 min_overhead = min(self.overhead_times)
309 max_overhead = max(self.overhead_times)
310 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000311 print('Calib. overhead time = %.6fms' % (
312 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000313 if min_overhead < 0.0:
314 raise ValueError('calibration setup did not work')
315 if max_overhead - min_overhead > 0.1:
316 raise ValueError(
317 'overhead calibration timing range too inaccurate: '
318 '%r - %r' % (min_overhead, max_overhead))
319
320 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000321
322 """ Run the test in two phases: first calibrate, then
323 do the actual test. Be careful to keep the calibration
324 timing low w/r to the test timing.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000325
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000326 """
327 test = self.test
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000328 timer = self.get_timer()
329
330 # Get calibration
331 min_overhead = min(self.overhead_times)
332
333 # Test run
334 t = timer()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000335 test()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000336 t = timer() - t
337 if t < MIN_TEST_RUNTIME:
338 raise ValueError('warp factor too high: '
339 'test times are < 10ms')
340 eff_time = t - min_overhead
341 if eff_time < 0:
342 raise ValueError('wrong calibration')
343 self.last_timing = (eff_time, t, min_overhead)
344 self.times.append(eff_time)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000345
346 def calibrate(self):
347
Thomas Wouters477c8d52006-05-27 19:21:47 +0000348 """ Calibrate the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000349
Thomas Wouters477c8d52006-05-27 19:21:47 +0000350 This method should execute everything that is needed to
351 setup and run the test - except for the actual operations
352 that you intend to measure. pybench uses this method to
353 measure the test implementation overhead.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000354
355 """
356 return
357
358 def test(self):
359
Thomas Wouters477c8d52006-05-27 19:21:47 +0000360 """ Run the test.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000361
Thomas Wouters477c8d52006-05-27 19:21:47 +0000362 The test needs to run self.rounds executing
363 self.operations number of operations each.
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000364
365 """
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000366 return
Thomas Wouters477c8d52006-05-27 19:21:47 +0000367
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000368 def stat(self):
369
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000370 """ Return test run statistics as tuple:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000371
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000372 (minimum run time,
373 average run time,
374 total run time,
375 average time per operation,
376 minimum overhead time)
377
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000378 """
379 runs = len(self.times)
380 if runs == 0:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000381 return 0.0, 0.0, 0.0, 0.0
382 min_time = min(self.times)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000383 total_time = sum(self.times)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000384 avg_time = total_time / float(runs)
385 operation_avg = total_time / float(runs
386 * self.rounds
387 * self.operations)
388 if self.overhead_times:
389 min_overhead = min(self.overhead_times)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000390 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000391 min_overhead = self.last_timing[2]
392 return min_time, avg_time, total_time, operation_avg, min_overhead
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000393
394### Load Setup
395
396# This has to be done after the definition of the Test class, since
397# the Setup module will import subclasses using this class.
398
399import Setup
400
401### Benchmark base class
402
403class Benchmark:
404
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000405 # Name of the benchmark
406 name = ''
407
408 # Number of benchmark rounds to run
409 rounds = 1
410
411 # Warp factor use to run the tests
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000412 warp = 1 # Warp factor
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000413
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000414 # Average benchmark round time
415 roundtime = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000416
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000417 # Benchmark version number as float x.yy
Antoine Pitrou8a681222009-02-07 17:13:31 +0000418 version = 2.1
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000419
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000420 # Produce verbose output ?
421 verbose = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000422
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000423 # Dictionary with the machine details
424 machine_details = None
425
426 # Timer used for the benchmark
427 timer = TIMER_PLATFORM_DEFAULT
428
429 def __init__(self, name, verbose=None, timer=None, warp=None,
430 calibration_runs=None):
431
432 if name:
433 self.name = name
Thomas Wouters477c8d52006-05-27 19:21:47 +0000434 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000435 self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \
436 (time.localtime(time.time())[:6])
437 if verbose is not None:
438 self.verbose = verbose
439 if timer is not None:
440 self.timer = timer
441 if warp is not None:
442 self.warp = warp
443 if calibration_runs is not None:
444 self.calibration_runs = calibration_runs
445
446 # Init vars
447 self.tests = {}
448 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000449 print('Getting machine details...')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000450 self.machine_details = get_machine_details()
451
452 # Make .version an instance attribute to have it saved in the
453 # Benchmark pickle
454 self.version = self.version
455
456 def get_timer(self):
457
458 """ Return the timer function to use for the test.
459
460 """
461 return get_timer(self.timer)
462
463 def compatible(self, other):
464
465 """ Return 1/0 depending on whether the benchmark is
466 compatible with the other Benchmark instance or not.
467
468 """
469 if self.version != other.version:
470 return 0
471 if (self.machine_details == other.machine_details and
472 self.timer != other.timer):
473 return 0
474 if (self.calibration_runs == 0 and
475 other.calibration_runs != 0):
476 return 0
477 if (self.calibration_runs != 0 and
478 other.calibration_runs == 0):
479 return 0
480 return 1
481
482 def load_tests(self, setupmod, limitnames=None):
483
484 # Add tests
485 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000486 print('Searching for tests ...')
487 print('--------------------------------------')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000488 for testclass in setupmod.__dict__.values():
489 if not hasattr(testclass, 'is_a_test'):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000490 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000491 name = testclass.__name__
Thomas Wouters477c8d52006-05-27 19:21:47 +0000492 if name == 'Test':
493 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000494 if (limitnames is not None and
495 limitnames.search(name) is None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000496 continue
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000497 self.tests[name] = testclass(
498 warp=self.warp,
499 calibration_runs=self.calibration_runs,
500 timer=self.timer)
Guido van Rossum486364b2007-06-30 05:01:58 +0000501 l = sorted(self.tests)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000502 if self.verbose:
503 for name in l:
Guido van Rossum486364b2007-06-30 05:01:58 +0000504 print(' %s' % name)
505 print('--------------------------------------')
506 print(' %i tests found' % len(l))
507 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000508
509 def calibrate(self):
510
Guido van Rossum486364b2007-06-30 05:01:58 +0000511 print('Calibrating tests. Please wait...', end=' ')
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000512 sys.stdout.flush()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000513 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000514 print()
515 print()
516 print('Test min max')
517 print('-' * LINE)
518 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000519 for i in range(len(tests)):
520 name, test = tests[i]
521 test.calibrate_test()
522 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000523 print('%30s: %6.3fms %6.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000524 (name,
525 min(test.overhead_times) * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000526 max(test.overhead_times) * MILLI_SECONDS))
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000527 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000528 print()
529 print('Done with the calibration.')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000530 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000531 print('done.')
532 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000533
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000534 def run(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000535
Guido van Rossum486364b2007-06-30 05:01:58 +0000536 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000537 timer = self.get_timer()
Guido van Rossum486364b2007-06-30 05:01:58 +0000538 print('Running %i round(s) of the suite at warp factor %i:' % \
539 (self.rounds, self.warp))
540 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000541 self.roundtimes = []
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000542 for i in range(self.rounds):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000543 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000544 print(' Round %-25i effective absolute overhead' % (i+1))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000545 total_eff_time = 0.0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000546 for j in range(len(tests)):
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 name, test = tests[j]
548 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000549 print('%30s:' % name, end=' ')
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000550 test.run()
551 (eff_time, abs_time, min_overhead) = test.last_timing
552 total_eff_time = total_eff_time + eff_time
553 if self.verbose:
Guido van Rossum486364b2007-06-30 05:01:58 +0000554 print(' %5.0fms %5.0fms %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000555 (eff_time * MILLI_SECONDS,
556 abs_time * MILLI_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000557 min_overhead * MILLI_SECONDS))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000558 self.roundtimes.append(total_eff_time)
559 if self.verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +0000560 print(' '
561 ' ------------------------------')
562 print(' '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000563 ' Totals: %6.0fms' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000564 (total_eff_time * MILLI_SECONDS))
Guido van Rossum486364b2007-06-30 05:01:58 +0000565 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000566 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000567 print('* Round %i done in %.3f seconds.' % (i+1,
568 total_eff_time))
569 print()
Thomas Wouters477c8d52006-05-27 19:21:47 +0000570
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000571 def stat(self):
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000572
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000573 """ Return benchmark run statistics as tuple:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000574
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000575 (minimum round time,
576 average round time,
577 maximum round time)
578
579 XXX Currently not used, since the benchmark does test
580 statistics across all rounds.
581
582 """
583 runs = len(self.roundtimes)
584 if runs == 0:
585 return 0.0, 0.0
586 min_time = min(self.roundtimes)
Guido van Rossum89da5d72006-08-22 00:21:25 +0000587 total_time = sum(self.roundtimes)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000588 avg_time = total_time / float(runs)
589 max_time = max(self.roundtimes)
590 return (min_time, avg_time, max_time)
591
592 def print_header(self, title='Benchmark'):
593
Guido van Rossum486364b2007-06-30 05:01:58 +0000594 print('-' * LINE)
595 print('%s: %s' % (title, self.name))
596 print('-' * LINE)
597 print()
598 print(' Rounds: %s' % self.rounds)
599 print(' Warp: %s' % self.warp)
600 print(' Timer: %s' % self.timer)
601 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000602 if self.machine_details:
603 print_machine_details(self.machine_details, indent=' ')
Guido van Rossum486364b2007-06-30 05:01:58 +0000604 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000605
606 def print_benchmark(self, hidenoise=0, limitnames=None):
607
Collin Winter6afaeb72007-08-03 17:06:41 +0000608 print('Test '
609 ' minimum average operation overhead')
Guido van Rossum486364b2007-06-30 05:01:58 +0000610 print('-' * LINE)
611 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000612 total_min_time = 0.0
613 total_avg_time = 0.0
614 for name, test in tests:
615 if (limitnames is not None and
616 limitnames.search(name) is None):
617 continue
618 (min_time,
619 avg_time,
620 total_time,
621 op_avg,
622 min_overhead) = test.stat()
623 total_min_time = total_min_time + min_time
624 total_avg_time = total_avg_time + avg_time
Guido van Rossum486364b2007-06-30 05:01:58 +0000625 print('%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000626 (name,
627 min_time * MILLI_SECONDS,
628 avg_time * MILLI_SECONDS,
629 op_avg * MICRO_SECONDS,
Guido van Rossum486364b2007-06-30 05:01:58 +0000630 min_overhead *MILLI_SECONDS))
631 print('-' * LINE)
Collin Winter6afaeb72007-08-03 17:06:41 +0000632 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000633 ' %6.0fms %6.0fms' %
634 (total_min_time * MILLI_SECONDS,
635 total_avg_time * MILLI_SECONDS,
Collin Winter6afaeb72007-08-03 17:06:41 +0000636 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000637 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000638
639 def print_comparison(self, compare_to, hidenoise=0, limitnames=None):
640
641 # Check benchmark versions
642 if compare_to.version != self.version:
Collin Winter6afaeb72007-08-03 17:06:41 +0000643 print('* Benchmark versions differ: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000644 'cannot compare this benchmark to "%s" !' %
Collin Winter6afaeb72007-08-03 17:06:41 +0000645 compare_to.name)
Guido van Rossum486364b2007-06-30 05:01:58 +0000646 print()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000647 self.print_benchmark(hidenoise=hidenoise,
648 limitnames=limitnames)
649 return
650
651 # Print header
652 compare_to.print_header('Comparing with')
Collin Winter6afaeb72007-08-03 17:06:41 +0000653 print('Test '
654 ' minimum run-time average run-time')
655 print(' '
656 ' this other diff this other diff')
Guido van Rossum486364b2007-06-30 05:01:58 +0000657 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000658
659 # Print test comparisons
Guido van Rossum486364b2007-06-30 05:01:58 +0000660 tests = sorted(self.tests.items())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000661 total_min_time = other_total_min_time = 0.0
662 total_avg_time = other_total_avg_time = 0.0
663 benchmarks_compatible = self.compatible(compare_to)
664 tests_compatible = 1
665 for name, test in tests:
666 if (limitnames is not None and
667 limitnames.search(name) is None):
668 continue
669 (min_time,
670 avg_time,
671 total_time,
672 op_avg,
673 min_overhead) = test.stat()
674 total_min_time = total_min_time + min_time
675 total_avg_time = total_avg_time + avg_time
676 try:
677 other = compare_to.tests[name]
678 except KeyError:
679 other = None
680 if other is None:
681 # Other benchmark doesn't include the given test
682 min_diff, avg_diff = 'n/a', 'n/a'
683 other_min_time = 0.0
684 other_avg_time = 0.0
685 tests_compatible = 0
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000686 else:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000687 (other_min_time,
688 other_avg_time,
689 other_total_time,
690 other_op_avg,
691 other_min_overhead) = other.stat()
692 other_total_min_time = other_total_min_time + other_min_time
693 other_total_avg_time = other_total_avg_time + other_avg_time
694 if (benchmarks_compatible and
695 test.compatible(other)):
Ezio Melotti42da6632011-03-15 05:18:48 +0200696 # Both benchmark and tests are comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000697 min_diff = ((min_time * self.warp) /
698 (other_min_time * other.warp) - 1.0)
699 avg_diff = ((avg_time * self.warp) /
700 (other_avg_time * other.warp) - 1.0)
701 if hidenoise and abs(min_diff) < 10.0:
702 min_diff = ''
703 else:
704 min_diff = '%+5.1f%%' % (min_diff * PERCENT)
705 if hidenoise and abs(avg_diff) < 10.0:
706 avg_diff = ''
707 else:
708 avg_diff = '%+5.1f%%' % (avg_diff * PERCENT)
709 else:
Ezio Melotti42da6632011-03-15 05:18:48 +0200710 # Benchmark or tests are not comparable
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000711 min_diff, avg_diff = 'n/a', 'n/a'
712 tests_compatible = 0
Guido van Rossum486364b2007-06-30 05:01:58 +0000713 print('%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000714 (name,
715 min_time * MILLI_SECONDS,
716 other_min_time * MILLI_SECONDS * compare_to.warp / self.warp,
717 min_diff,
718 avg_time * MILLI_SECONDS,
719 other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp,
Guido van Rossum486364b2007-06-30 05:01:58 +0000720 avg_diff))
721 print('-' * LINE)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000722
723 # Summarise test results
724 if not benchmarks_compatible or not tests_compatible:
725 min_diff, avg_diff = 'n/a', 'n/a'
726 else:
727 if other_total_min_time != 0.0:
728 min_diff = '%+5.1f%%' % (
729 ((total_min_time * self.warp) /
730 (other_total_min_time * compare_to.warp) - 1.0) * PERCENT)
731 else:
732 min_diff = 'n/a'
733 if other_total_avg_time != 0.0:
734 avg_diff = '%+5.1f%%' % (
735 ((total_avg_time * self.warp) /
736 (other_total_avg_time * compare_to.warp) - 1.0) * PERCENT)
737 else:
738 avg_diff = 'n/a'
Collin Winter6afaeb72007-08-03 17:06:41 +0000739 print('Totals: '
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000740 ' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' %
741 (total_min_time * MILLI_SECONDS,
742 (other_total_min_time * compare_to.warp/self.warp
743 * MILLI_SECONDS),
744 min_diff,
745 total_avg_time * MILLI_SECONDS,
746 (other_total_avg_time * compare_to.warp/self.warp
747 * MILLI_SECONDS),
748 avg_diff
Collin Winter6afaeb72007-08-03 17:06:41 +0000749 ))
Guido van Rossum486364b2007-06-30 05:01:58 +0000750 print()
751 print('(this=%s, other=%s)' % (self.name,
752 compare_to.name))
753 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000754
755class PyBenchCmdline(Application):
756
757 header = ("PYBENCH - a benchmark test suite for Python "
758 "interpreters/compilers.")
759
760 version = __version__
761
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000762 debug = _debug
763
764 options = [ArgumentOption('-n',
765 'number of rounds',
766 Setup.Number_of_rounds),
767 ArgumentOption('-f',
768 'save benchmark to file arg',
769 ''),
770 ArgumentOption('-c',
771 'compare benchmark with the one in file arg',
772 ''),
773 ArgumentOption('-s',
774 'show benchmark in file arg, then exit',
775 ''),
776 ArgumentOption('-w',
777 'set warp factor to arg',
778 Setup.Warp_factor),
779 ArgumentOption('-t',
780 'run only tests with names matching arg',
781 ''),
782 ArgumentOption('-C',
783 'set the number of calibration runs to arg',
784 CALIBRATION_RUNS),
785 SwitchOption('-d',
786 'hide noise in comparisons',
787 0),
788 SwitchOption('-v',
789 'verbose output (not recommended)',
790 0),
791 SwitchOption('--with-gc',
792 'enable garbage collection',
793 0),
794 SwitchOption('--with-syscheck',
795 'use default sys check interval',
796 0),
797 ArgumentOption('--timer',
798 'use given timer',
799 TIMER_PLATFORM_DEFAULT),
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000800 ]
801
802 about = """\
803The normal operation is to run the suite and display the
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000804results. Use -f to save them for later reuse or comparisons.
805
806Available timers:
807
808 time.time
809 time.clock
810 systimes.processtime
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000811
812Examples:
813
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000814python2.1 pybench.py -f p21.pybench
815python2.5 pybench.py -f p25.pybench
816python pybench.py -s p25.pybench -c p21.pybench
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000817"""
818 copyright = __copyright__
819
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000820 def main(self):
821
822 rounds = self.values['-n']
823 reportfile = self.values['-f']
824 show_bench = self.values['-s']
825 compare_to = self.values['-c']
826 hidenoise = self.values['-d']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000827 warp = int(self.values['-w'])
828 withgc = self.values['--with-gc']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000829 limitnames = self.values['-t']
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000830 if limitnames:
831 if _debug:
Guido van Rossum486364b2007-06-30 05:01:58 +0000832 print('* limiting test names to one with substring "%s"' % \
833 limitnames)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000834 limitnames = re.compile(limitnames, re.I)
835 else:
836 limitnames = None
Thomas Wouters477c8d52006-05-27 19:21:47 +0000837 verbose = self.verbose
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000838 withsyscheck = self.values['--with-syscheck']
839 calibration_runs = self.values['-C']
840 timer = self.values['--timer']
Thomas Wouters477c8d52006-05-27 19:21:47 +0000841
Guido van Rossum486364b2007-06-30 05:01:58 +0000842 print('-' * LINE)
843 print('PYBENCH %s' % __version__)
844 print('-' * LINE)
845 print('* using %s %s' % (
Christian Heimesdd15f6c2008-03-16 00:07:10 +0000846 getattr(platform, 'python_implementation', lambda:'Python')(),
Guido van Rossum486364b2007-06-30 05:01:58 +0000847 ' '.join(sys.version.split())))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000848
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000849 # Switch off garbage collection
850 if not withgc:
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000851 try:
852 import gc
853 except ImportError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000854 print('* Python version doesn\'t support garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000855 else:
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000856 try:
857 gc.disable()
858 except NotImplementedError:
Guido van Rossum486364b2007-06-30 05:01:58 +0000859 print('* Python version doesn\'t support gc.disable')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000860 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000861 print('* disabled garbage collection')
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000862
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000863 # "Disable" sys check interval
864 if not withsyscheck:
865 # Too bad the check interval uses an int instead of a long...
866 value = 2147483647
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000867 try:
868 sys.setcheckinterval(value)
869 except (AttributeError, NotImplementedError):
Guido van Rossum486364b2007-06-30 05:01:58 +0000870 print('* Python version doesn\'t support sys.setcheckinterval')
Thomas Woutersfc7bb8c2007-01-15 15:49:28 +0000871 else:
Guido van Rossum486364b2007-06-30 05:01:58 +0000872 print('* system check interval set to maximum: %s' % value)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000873
874 if timer == TIMER_SYSTIMES_PROCESSTIME:
875 import systimes
Guido van Rossum486364b2007-06-30 05:01:58 +0000876 print('* using timer: systimes.processtime (%s)' % \
877 systimes.SYSTIMES_IMPLEMENTATION)
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000878 else:
Victor Stinnerfe98e2f2012-04-29 03:01:20 +0200879 # Check that the clock function does exist
880 try:
881 get_timer(timer)
882 except TypeError:
883 print("* Error: Unknown timer: %s" % timer)
884 return
885
Guido van Rossum486364b2007-06-30 05:01:58 +0000886 print('* using timer: %s' % timer)
Victor Stinnerfe98e2f2012-04-29 03:01:20 +0200887 if hasattr(time, 'get_clock_info'):
888 info = time.get_clock_info(timer[5:])
889 print('* timer: resolution=%s, implementation=%s'
890 % (info.resolution, info.implementation))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000891
Guido van Rossum486364b2007-06-30 05:01:58 +0000892 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000893
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000894 if compare_to:
895 try:
896 f = open(compare_to,'rb')
897 bench = pickle.load(f)
898 bench.name = compare_to
899 f.close()
900 compare_to = bench
Guido van Rossumb940e112007-01-10 16:19:56 +0000901 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000902 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000903 repr(compare_to),
Guido van Rossum486364b2007-06-30 05:01:58 +0000904 reason))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000905 compare_to = None
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000906
907 if show_bench:
908 try:
909 f = open(show_bench,'rb')
910 bench = pickle.load(f)
911 bench.name = show_bench
912 f.close()
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000913 bench.print_header()
914 if compare_to:
915 bench.print_comparison(compare_to,
916 hidenoise=hidenoise,
917 limitnames=limitnames)
918 else:
919 bench.print_benchmark(hidenoise=hidenoise,
920 limitnames=limitnames)
Guido van Rossumb940e112007-01-10 16:19:56 +0000921 except IOError as reason:
Guido van Rossum486364b2007-06-30 05:01:58 +0000922 print('* Error opening/reading file %s: %s' % (
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000923 repr(show_bench),
Guido van Rossum486364b2007-06-30 05:01:58 +0000924 reason))
925 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000926 return
927
928 if reportfile:
Guido van Rossum486364b2007-06-30 05:01:58 +0000929 print('Creating benchmark: %s (rounds=%i, warp=%i)' % \
930 (reportfile, rounds, warp))
931 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000932
933 # Create benchmark object
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000934 bench = Benchmark(reportfile,
935 verbose=verbose,
936 timer=timer,
937 warp=warp,
938 calibration_runs=calibration_runs)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000939 bench.rounds = rounds
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000940 bench.load_tests(Setup, limitnames=limitnames)
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000941 try:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000942 bench.calibrate()
943 bench.run()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000944 except KeyboardInterrupt:
Guido van Rossum486364b2007-06-30 05:01:58 +0000945 print()
946 print('*** KeyboardInterrupt -- Aborting')
947 print()
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000948 return
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000949 bench.print_header()
950 if compare_to:
951 bench.print_comparison(compare_to,
952 hidenoise=hidenoise,
953 limitnames=limitnames)
954 else:
955 bench.print_benchmark(hidenoise=hidenoise,
956 limitnames=limitnames)
957
958 # Ring bell
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000959 sys.stderr.write('\007')
960
961 if reportfile:
962 try:
963 f = open(reportfile,'wb')
964 bench.name = reportfile
965 pickle.dump(bench,f)
966 f.close()
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()