|
#!/usr/local/bin/python -O |
|
|
|
""" A Python Benchmark Suite |
|
|
|
""" |
|
# |
|
# Note: Please keep this module compatible to Python 1.5.2. |
|
# |
|
# Tests may include features in later Python versions, but these |
|
# should then be embedded in try-except clauses in the configuration |
|
# module Setup.py. |
|
# |
|
|
|
# pybench Copyright |
|
__copyright__ = """\ |
|
Copyright (c), 1997-2006, Marc-Andre Lemburg (mal@lemburg.com) |
|
Copyright (c), 2000-2006, eGenix.com Software GmbH (info@egenix.com) |
|
|
|
All Rights Reserved. |
|
|
|
Permission to use, copy, modify, and distribute this software and its |
|
documentation for any purpose and without fee or royalty is hereby |
|
granted, provided that the above copyright notice appear in all copies |
|
and that both that copyright notice and this permission notice appear |
|
in supporting documentation or portions thereof, including |
|
modifications, that you make. |
|
|
|
THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO |
|
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND |
|
FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, |
|
INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING |
|
FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
|
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION |
|
WITH THE USE OR PERFORMANCE OF THIS SOFTWARE ! |
|
""" |
|
|
|
import sys, time, operator, string, platform |
|
from CommandLine import * |
|
|
|
try: |
|
import cPickle |
|
pickle = cPickle |
|
except ImportError: |
|
import pickle |
|
|
|
# Version number; version history: see README file ! |
|
__version__ = '2.0' |
|
|
|
### Constants |
|
|
|
# Second fractions |
|
MILLI_SECONDS = 1e3 |
|
MICRO_SECONDS = 1e6 |
|
|
|
# Percent unit |
|
PERCENT = 100 |
|
|
|
# Horizontal line length |
|
LINE = 79 |
|
|
|
# Minimum test run-time |
|
MIN_TEST_RUNTIME = 1e-3 |
|
|
|
# Number of calibration runs to use for calibrating the tests |
|
CALIBRATION_RUNS = 20 |
|
|
|
# Number of calibration loops to run for each calibration run |
|
CALIBRATION_LOOPS = 20 |
|
|
|
# Allow skipping calibration ? |
|
ALLOW_SKIPPING_CALIBRATION = 1 |
|
|
|
# Timer types |
|
TIMER_TIME_TIME = 'time.time' |
|
TIMER_TIME_CLOCK = 'time.clock' |
|
TIMER_SYSTIMES_PROCESSTIME = 'systimes.processtime' |
|
|
|
# Choose platform default timer |
|
if sys.platform[:3] == 'win': |
|
# On WinXP this has 2.5ms resolution |
|
TIMER_PLATFORM_DEFAULT = TIMER_TIME_CLOCK |
|
else: |
|
# On Linux this has 1ms resolution |
|
TIMER_PLATFORM_DEFAULT = TIMER_TIME_TIME |
|
|
|
# Print debug information ? |
|
_debug = 0 |
|
|
|
### Helpers |
|
|
|
def get_timer(timertype): |
|
|
|
if timertype == TIMER_TIME_TIME: |
|
return time.time |
|
elif timertype == TIMER_TIME_CLOCK: |
|
return time.clock |
|
elif timertype == TIMER_SYSTIMES_PROCESSTIME: |
|
import systimes |
|
return systimes.processtime |
|
else: |
|
raise TypeError('unknown timer type: %s' % timertype) |
|
|
|
def get_machine_details(): |
|
|
|
if _debug: |
|
print 'Getting machine details...' |
|
buildno, builddate = platform.python_build() |
|
python = platform.python_version() |
|
try: |
|
unichr(100000) |
|
except ValueError: |
|
# UCS2 build (standard) |
|
unicode = 'UCS2' |
|
except NameError: |
|
unicode = None |
|
else: |
|
# UCS4 build (most recent Linux distros) |
|
unicode = 'UCS4' |
|
bits, linkage = platform.architecture() |
|
return { |
|
'platform': platform.platform(), |
|
'processor': platform.processor(), |
|
'executable': sys.executable, |
|
'implementation': getattr(platform, 'python_implementation', |
|
lambda:'n/a')(), |
|
'python': platform.python_version(), |
|
'compiler': platform.python_compiler(), |
|
'buildno': buildno, |
|
'builddate': builddate, |
|
'unicode': unicode, |
|
'bits': bits, |
|
} |
|
|
|
def print_machine_details(d, indent=''): |
|
|
|
l = ['Machine Details:', |
|
' Platform ID: %s' % d.get('platform', 'n/a'), |
|
' Processor: %s' % d.get('processor', 'n/a'), |
|
'', |
|
'Python:', |
|
' Implementation: %s' % d.get('implementation', 'n/a'), |
|
' Executable: %s' % d.get('executable', 'n/a'), |
|
' Version: %s' % d.get('python', 'n/a'), |
|
' Compiler: %s' % d.get('compiler', 'n/a'), |
|
' Bits: %s' % d.get('bits', 'n/a'), |
|
' Build: %s (#%s)' % (d.get('builddate', 'n/a'), |
|
d.get('buildno', 'n/a')), |
|
' Unicode: %s' % d.get('unicode', 'n/a'), |
|
] |
|
print indent + string.join(l, '\n' + indent) + '\n' |
|
|
|
### Test baseclass |
|
|
|
class Test: |
|
|
|
""" All test must have this class as baseclass. It provides |
|
the necessary interface to the benchmark machinery. |
|
|
|
The tests must set .rounds to a value high enough to let the |
|
test run between 20-50 seconds. This is needed because |
|
clock()-timing only gives rather inaccurate values (on Linux, |
|
for example, it is accurate to a few hundreths of a |
|
second). If you don't want to wait that long, use a warp |
|
factor larger than 1. |
|
|
|
It is also important to set the .operations variable to a |
|
value representing the number of "virtual operations" done per |
|
call of .run(). |
|
|
|
If you change a test in some way, don't forget to increase |
|
its version number. |
|
|
|
""" |
|
|
|
### Instance variables that each test should override |
|
|
|
# Version number of the test as float (x.yy); this is important |
|
# for comparisons of benchmark runs - tests with unequal version |
|
# number will not get compared. |
|
version = 2.0 |
|
|
|
# The number of abstract operations done in each round of the |
|
# test. An operation is the basic unit of what you want to |
|
# measure. The benchmark will output the amount of run-time per |
|
# operation. Note that in order to raise the measured timings |
|
# significantly above noise level, it is often required to repeat |
|
# sets of operations more than once per test round. The measured |
|
# overhead per test round should be less than 1 second. |
|
operations = 1 |
|
|
|
# Number of rounds to execute per test run. This should be |
|
# adjusted to a figure that results in a test run-time of between |
|
# 1-2 seconds. |
|
rounds = 100000 |
|
|
|
### Internal variables |
|
|
|
# Mark this class as implementing a test |
|
is_a_test = 1 |
|
|
|
# Last timing: (real, run, overhead) |
|
last_timing = (0.0, 0.0, 0.0) |
|
|
|
# Warp factor to use for this test |
|
warp = 1 |
|
|
|
# Number of calibration runs to use |
|
calibration_runs = CALIBRATION_RUNS |
|
|
|
# List of calibration timings |
|
overhead_times = None |
|
|
|
# List of test run timings |
|
times = [] |
|
|
|
# Timer used for the benchmark |
|
timer = TIMER_PLATFORM_DEFAULT |
|
|
|
def __init__(self, warp=None, calibration_runs=None, timer=None): |
|
|
|
# Set parameters |
|
if warp is not None: |
|
self.rounds = int(self.rounds / warp) |
|
if self.rounds == 0: |
|
raise ValueError('warp factor set too high') |
|
self.warp = warp |
|
if calibration_runs is not None: |
|
if (not ALLOW_SKIPPING_CALIBRATION and |
|
calibration_runs < 1): |
|
raise ValueError('at least one calibration run is required') |
|
self.calibration_runs = calibration_runs |
|
if timer is not None: |
|
self.timer = timer |
|
|
|
# Init variables |
|
self.times = [] |
|
self.overhead_times = [] |
|
|
|
# We want these to be in the instance dict, so that pickle |
|
# saves them |
|
self.version = self.version |
|
self.operations = self.operations |
|
self.rounds = self.rounds |
|
|
|
def get_timer(self): |
|
|
|
""" Return the timer function to use for the test. |
|
|
|
""" |
|
return get_timer(self.timer) |
|
|
|
def compatible(self, other): |
|
|
|
""" Return 1/0 depending on whether the test is compatible |
|
with the other Test instance or not. |
|
|
|
""" |
|
if self.version != other.version: |
|
return 0 |
|
if self.rounds != other.rounds: |
|
return 0 |
|
return 1 |
|
|
|
def calibrate_test(self): |
|
|
|
if self.calibration_runs == 0: |
|
self.overhead_times = [0.0] |
|
return |
|
|
|
calibrate = self.calibrate |
|
timer = self.get_timer() |
|
calibration_loops = range(CALIBRATION_LOOPS) |
|
|
|
# Time the calibration loop overhead |
|
prep_times = [] |
|
for i in range(self.calibration_runs): |
|
t = timer() |
|
for i in calibration_loops: |
|
pass |
|
t = timer() - t |
|
prep_times.append(t / CALIBRATION_LOOPS) |
|
min_prep_time = min(prep_times) |
|
if _debug: |
|
print |
|
print 'Calib. prep time = %.6fms' % ( |
|
min_prep_time * MILLI_SECONDS) |
|
|
|
# Time the calibration runs (doing CALIBRATION_LOOPS loops of |
|
# .calibrate() method calls each) |
|
for i in range(self.calibration_runs): |
|
t = timer() |
|
for i in calibration_loops: |
|
calibrate() |
|
t = timer() - t |
|
self.overhead_times.append(t / CALIBRATION_LOOPS |
|
- min_prep_time) |
|
|
|
# Check the measured times |
|
min_overhead = min(self.overhead_times) |
|
max_overhead = max(self.overhead_times) |
|
if _debug: |
|
print 'Calib. overhead time = %.6fms' % ( |
|
min_overhead * MILLI_SECONDS) |
|
if min_overhead < 0.0: |
|
raise ValueError('calibration setup did not work') |
|
if max_overhead - min_overhead > 0.1: |
|
raise ValueError( |
|
'overhead calibration timing range too inaccurate: ' |
|
'%r - %r' % (min_overhead, max_overhead)) |
|
|
|
def run(self): |
|
|
|
""" Run the test in two phases: first calibrate, then |
|
do the actual test. Be careful to keep the calibration |
|
timing low w/r to the test timing. |
|
|
|
""" |
|
test = self.test |
|
timer = self.get_timer() |
|
|
|
# Get calibration |
|
min_overhead = min(self.overhead_times) |
|
|
|
# Test run |
|
t = timer() |
|
test() |
|
t = timer() - t |
|
if t < MIN_TEST_RUNTIME: |
|
raise ValueError('warp factor too high: ' |
|
'test times are < 10ms') |
|
eff_time = t - min_overhead |
|
if eff_time < 0: |
|
raise ValueError('wrong calibration') |
|
self.last_timing = (eff_time, t, min_overhead) |
|
self.times.append(eff_time) |
|
|
|
def calibrate(self): |
|
|
|
""" Calibrate the test. |
|
|
|
This method should execute everything that is needed to |
|
setup and run the test - except for the actual operations |
|
that you intend to measure. pybench uses this method to |
|
measure the test implementation overhead. |
|
|
|
""" |
|
return |
|
|
|
def test(self): |
|
|
|
""" Run the test. |
|
|
|
The test needs to run self.rounds executing |
|
self.operations number of operations each. |
|
|
|
""" |
|
return |
|
|
|
def stat(self): |
|
|
|
""" Return test run statistics as tuple: |
|
|
|
(minimum run time, |
|
average run time, |
|
total run time, |
|
average time per operation, |
|
minimum overhead time) |
|
|
|
""" |
|
runs = len(self.times) |
|
if runs == 0: |
|
return 0.0, 0.0, 0.0, 0.0 |
|
min_time = min(self.times) |
|
total_time = reduce(operator.add, self.times, 0.0) |
|
avg_time = total_time / float(runs) |
|
operation_avg = total_time / float(runs |
|
* self.rounds |
|
* self.operations) |
|
if self.overhead_times: |
|
min_overhead = min(self.overhead_times) |
|
else: |
|
min_overhead = self.last_timing[2] |
|
return min_time, avg_time, total_time, operation_avg, min_overhead |
|
|
|
### Load Setup |
|
|
|
# This has to be done after the definition of the Test class, since |
|
# the Setup module will import subclasses using this class. |
|
|
|
import Setup |
|
|
|
### Benchmark base class |
|
|
|
class Benchmark: |
|
|
|
# Name of the benchmark |
|
name = '' |
|
|
|
# Number of benchmark rounds to run |
|
rounds = 1 |
|
|
|
# Warp factor use to run the tests |
|
warp = 1 # Warp factor |
|
|
|
# Average benchmark round time |
|
roundtime = 0 |
|
|
|
# Benchmark version number as float x.yy |
|
version = 2.0 |
|
|
|
# Produce verbose output ? |
|
verbose = 0 |
|
|
|
# Dictionary with the machine details |
|
machine_details = None |
|
|
|
# Timer used for the benchmark |
|
timer = TIMER_PLATFORM_DEFAULT |
|
|
|
def __init__(self, name, verbose=None, timer=None, warp=None, |
|
calibration_runs=None): |
|
|
|
if name: |
|
self.name = name |
|
else: |
|
self.name = '%04i-%02i-%02i %02i:%02i:%02i' % \ |
|
(time.localtime(time.time())[:6]) |
|
if verbose is not None: |
|
self.verbose = verbose |
|
if timer is not None: |
|
self.timer = timer |
|
if warp is not None: |
|
self.warp = warp |
|
if calibration_runs is not None: |
|
self.calibration_runs = calibration_runs |
|
|
|
# Init vars |
|
self.tests = {} |
|
if _debug: |
|
print 'Getting machine details...' |
|
self.machine_details = get_machine_details() |
|
|
|
# Make .version an instance attribute to have it saved in the |
|
# Benchmark pickle |
|
self.version = self.version |
|
|
|
def get_timer(self): |
|
|
|
""" Return the timer function to use for the test. |
|
|
|
""" |
|
return get_timer(self.timer) |
|
|
|
def compatible(self, other): |
|
|
|
""" Return 1/0 depending on whether the benchmark is |
|
compatible with the other Benchmark instance or not. |
|
|
|
""" |
|
if self.version != other.version: |
|
return 0 |
|
if (self.machine_details == other.machine_details and |
|
self.timer != other.timer): |
|
return 0 |
|
if (self.calibration_runs == 0 and |
|
other.calibration_runs != 0): |
|
return 0 |
|
if (self.calibration_runs != 0 and |
|
other.calibration_runs == 0): |
|
return 0 |
|
return 1 |
|
|
|
def load_tests(self, setupmod, limitnames=None): |
|
|
|
# Add tests |
|
if self.verbose: |
|
print 'Searching for tests ...' |
|
print '--------------------------------------' |
|
for testclass in setupmod.__dict__.values(): |
|
if not hasattr(testclass, 'is_a_test'): |
|
continue |
|
name = testclass.__name__ |
|
if name == 'Test': |
|
continue |
|
if (limitnames is not None and |
|
limitnames.search(name) is None): |
|
continue |
|
self.tests[name] = testclass( |
|
warp=self.warp, |
|
calibration_runs=self.calibration_runs, |
|
timer=self.timer) |
|
l = self.tests.keys() |
|
l.sort() |
|
if self.verbose: |
|
for name in l: |
|
print ' %s' % name |
|
print '--------------------------------------' |
|
print ' %i tests found' % len(l) |
|
print |
|
|
|
def calibrate(self): |
|
|
|
print 'Calibrating tests. Please wait...', |
|
sys.stdout.flush() |
|
if self.verbose: |
|
print |
|
print |
|
print 'Test min max' |
|
print '-' * LINE |
|
tests = self.tests.items() |
|
tests.sort() |
|
for i in range(len(tests)): |
|
name, test = tests[i] |
|
test.calibrate_test() |
|
if self.verbose: |
|
print '%30s: %6.3fms %6.3fms' % \ |
|
(name, |
|
min(test.overhead_times) * MILLI_SECONDS, |
|
max(test.overhead_times) * MILLI_SECONDS) |
|
if self.verbose: |
|
print |
|
print 'Done with the calibration.' |
|
else: |
|
print 'done.' |
|
print |
|
|
|
def run(self): |
|
|
|
tests = self.tests.items() |
|
tests.sort() |
|
timer = self.get_timer() |
|
print 'Running %i round(s) of the suite at warp factor %i:' % \ |
|
(self.rounds, self.warp) |
|
print |
|
self.roundtimes = [] |
|
for i in range(self.rounds): |
|
if self.verbose: |
|
print ' Round %-25i effective absolute overhead' % (i+1) |
|
total_eff_time = 0.0 |
|
for j in range(len(tests)): |
|
name, test = tests[j] |
|
if self.verbose: |
|
print '%30s:' % name, |
|
test.run() |
|
(eff_time, abs_time, min_overhead) = test.last_timing |
|
total_eff_time = total_eff_time + eff_time |
|
if self.verbose: |
|
print ' %5.0fms %5.0fms %7.3fms' % \ |
|
(eff_time * MILLI_SECONDS, |
|
abs_time * MILLI_SECONDS, |
|
min_overhead * MILLI_SECONDS) |
|
self.roundtimes.append(total_eff_time) |
|
if self.verbose: |
|
print (' ' |
|
' ------------------------------') |
|
print (' ' |
|
' Totals: %6.0fms' % |
|
(total_eff_time * MILLI_SECONDS)) |
|
print |
|
else: |
|
print '* Round %i done in %.3f seconds.' % (i+1, |
|
total_eff_time) |
|
print |
|
|
|
def stat(self): |
|
|
|
""" Return benchmark run statistics as tuple: |
|
|
|
(minimum round time, |
|
average round time, |
|
maximum round time) |
|
|
|
XXX Currently not used, since the benchmark does test |
|
statistics across all rounds. |
|
|
|
""" |
|
runs = len(self.roundtimes) |
|
if runs == 0: |
|
return 0.0, 0.0 |
|
min_time = min(self.roundtimes) |
|
total_time = reduce(operator.add, self.roundtimes, 0.0) |
|
avg_time = total_time / float(runs) |
|
max_time = max(self.roundtimes) |
|
return (min_time, avg_time, max_time) |
|
|
|
def print_header(self, title='Benchmark'): |
|
|
|
print '-' * LINE |
|
print '%s: %s' % (title, self.name) |
|
print '-' * LINE |
|
print |
|
print ' Rounds: %s' % self.rounds |
|
print ' Warp: %s' % self.warp |
|
print ' Timer: %s' % self.timer |
|
print |
|
if self.machine_details: |
|
print_machine_details(self.machine_details, indent=' ') |
|
print |
|
|
|
def print_benchmark(self, hidenoise=0, limitnames=None): |
|
|
|
print ('Test ' |
|
' minimum average operation overhead') |
|
print '-' * LINE |
|
tests = self.tests.items() |
|
tests.sort() |
|
total_min_time = 0.0 |
|
total_avg_time = 0.0 |
|
for name, test in tests: |
|
if (limitnames is not None and |
|
limitnames.search(name) is None): |
|
continue |
|
(min_time, |
|
avg_time, |
|
total_time, |
|
op_avg, |
|
min_overhead) = test.stat() |
|
total_min_time = total_min_time + min_time |
|
total_avg_time = total_avg_time + avg_time |
|
print '%30s: %5.0fms %5.0fms %6.2fus %7.3fms' % \ |
|
(name, |
|
min_time * MILLI_SECONDS, |
|
avg_time * MILLI_SECONDS, |
|
op_avg * MICRO_SECONDS, |
|
min_overhead *MILLI_SECONDS) |
|
print '-' * LINE |
|
print ('Totals: ' |
|
' %6.0fms %6.0fms' % |
|
(total_min_time * MILLI_SECONDS, |
|
total_avg_time * MILLI_SECONDS, |
|
)) |
|
print |
|
|
|
def print_comparison(self, compare_to, hidenoise=0, limitnames=None): |
|
|
|
# Check benchmark versions |
|
if compare_to.version != self.version: |
|
print ('* Benchmark versions differ: ' |
|
'cannot compare this benchmark to "%s" !' % |
|
compare_to.name) |
|
print |
|
self.print_benchmark(hidenoise=hidenoise, |
|
limitnames=limitnames) |
|
return |
|
|
|
# Print header |
|
compare_to.print_header('Comparing with') |
|
print ('Test ' |
|
' minimum run-time average run-time') |
|
print (' ' |
|
' this other diff this other diff') |
|
print '-' * LINE |
|
|
|
# Print test comparisons |
|
tests = self.tests.items() |
|
tests.sort() |
|
total_min_time = other_total_min_time = 0.0 |
|
total_avg_time = other_total_avg_time = 0.0 |
|
benchmarks_compatible = self.compatible(compare_to) |
|
tests_compatible = 1 |
|
for name, test in tests: |
|
if (limitnames is not None and |
|
limitnames.search(name) is None): |
|
continue |
|
(min_time, |
|
avg_time, |
|
total_time, |
|
op_avg, |
|
min_overhead) = test.stat() |
|
total_min_time = total_min_time + min_time |
|
total_avg_time = total_avg_time + avg_time |
|
try: |
|
other = compare_to.tests[name] |
|
except KeyError: |
|
other = None |
|
if other is None: |
|
# Other benchmark doesn't include the given test |
|
min_diff, avg_diff = 'n/a', 'n/a' |
|
other_min_time = 0.0 |
|
other_avg_time = 0.0 |
|
tests_compatible = 0 |
|
else: |
|
(other_min_time, |
|
other_avg_time, |
|
other_total_time, |
|
other_op_avg, |
|
other_min_overhead) = other.stat() |
|
other_total_min_time = other_total_min_time + other_min_time |
|
other_total_avg_time = other_total_avg_time + other_avg_time |
|
if (benchmarks_compatible and |
|
test.compatible(other)): |
|
# Both benchmark and tests are comparable |
|
min_diff = ((min_time * self.warp) / |
|
(other_min_time * other.warp) - 1.0) |
|
avg_diff = ((avg_time * self.warp) / |
|
(other_avg_time * other.warp) - 1.0) |
|
if hidenoise and abs(min_diff) < 10.0: |
|
min_diff = '' |
|
else: |
|
min_diff = '%+5.1f%%' % (min_diff * PERCENT) |
|
if hidenoise and abs(avg_diff) < 10.0: |
|
avg_diff = '' |
|
else: |
|
avg_diff = '%+5.1f%%' % (avg_diff * PERCENT) |
|
else: |
|
# Benchmark or tests are not comparable |
|
min_diff, avg_diff = 'n/a', 'n/a' |
|
tests_compatible = 0 |
|
print '%30s: %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % \ |
|
(name, |
|
min_time * MILLI_SECONDS, |
|
other_min_time * MILLI_SECONDS * compare_to.warp / self.warp, |
|
min_diff, |
|
avg_time * MILLI_SECONDS, |
|
other_avg_time * MILLI_SECONDS * compare_to.warp / self.warp, |
|
avg_diff) |
|
print '-' * LINE |
|
|
|
# Summarise test results |
|
if not benchmarks_compatible or not tests_compatible: |
|
min_diff, avg_diff = 'n/a', 'n/a' |
|
else: |
|
if other_total_min_time != 0.0: |
|
min_diff = '%+5.1f%%' % ( |
|
((total_min_time * self.warp) / |
|
(other_total_min_time * compare_to.warp) - 1.0) * PERCENT) |
|
else: |
|
min_diff = 'n/a' |
|
if other_total_avg_time != 0.0: |
|
avg_diff = '%+5.1f%%' % ( |
|
((total_avg_time * self.warp) / |
|
(other_total_avg_time * compare_to.warp) - 1.0) * PERCENT) |
|
else: |
|
avg_diff = 'n/a' |
|
print ('Totals: ' |
|
' %5.0fms %5.0fms %7s %5.0fms %5.0fms %7s' % |
|
(total_min_time * MILLI_SECONDS, |
|
(other_total_min_time * compare_to.warp/self.warp |
|
* MILLI_SECONDS), |
|
min_diff, |
|
total_avg_time * MILLI_SECONDS, |
|
(other_total_avg_time * compare_to.warp/self.warp |
|
* MILLI_SECONDS), |
|
avg_diff |
|
)) |
|
print |
|
print '(this=%s, other=%s)' % (self.name, |
|
compare_to.name) |
|
print |
|
|
|
class PyBenchCmdline(Application): |
|
|
|
header = ("PYBENCH - a benchmark test suite for Python " |
|
"interpreters/compilers.") |
|
|
|
version = __version__ |
|
|
|
debug = _debug |
|
|
|
options = [ArgumentOption('-n', |
|
'number of rounds', |
|
Setup.Number_of_rounds), |
|
ArgumentOption('-f', |
|
'save benchmark to file arg', |
|
''), |
|
ArgumentOption('-c', |
|
'compare benchmark with the one in file arg', |
|
''), |
|
ArgumentOption('-s', |
|
'show benchmark in file arg, then exit', |
|
''), |
|
ArgumentOption('-w', |
|
'set warp factor to arg', |
|
Setup.Warp_factor), |
|
ArgumentOption('-t', |
|
'run only tests with names matching arg', |
|
''), |
|
ArgumentOption('-C', |
|
'set the number of calibration runs to arg', |
|
CALIBRATION_RUNS), |
|
SwitchOption('-d', |
|
'hide noise in comparisons', |
|
0), |
|
SwitchOption('-v', |
|
'verbose output (not recommended)', |
|
0), |
|
SwitchOption('--with-gc', |
|
'enable garbage collection', |
|
0), |
|
SwitchOption('--with-syscheck', |
|
'use default sys check interval', |
|
0), |
|
ArgumentOption('--timer', |
|
'use given timer', |
|
TIMER_PLATFORM_DEFAULT), |
|
] |
|
|
|
about = """\ |
|
The normal operation is to run the suite and display the |
|
results. Use -f to save them for later reuse or comparisons. |
|
|
|
Available timers: |
|
|
|
time.time |
|
time.clock |
|
systimes.processtime |
|
|
|
Examples: |
|
|
|
python2.1 pybench.py -f p21.pybench |
|
python2.5 pybench.py -f p25.pybench |
|
python pybench.py -s p25.pybench -c p21.pybench |
|
""" |
|
copyright = __copyright__ |
|
|
|
def main(self): |
|
|
|
rounds = self.values['-n'] |
|
reportfile = self.values['-f'] |
|
show_bench = self.values['-s'] |
|
compare_to = self.values['-c'] |
|
hidenoise = self.values['-d'] |
|
warp = int(self.values['-w']) |
|
withgc = self.values['--with-gc'] |
|
limitnames = self.values['-t'] |
|
if limitnames: |
|
if _debug: |
|
print '* limiting test names to one with substring "%s"' % \ |
|
limitnames |
|
limitnames = re.compile(limitnames, re.I) |
|
else: |
|
limitnames = None |
|
verbose = self.verbose |
|
withsyscheck = self.values['--with-syscheck'] |
|
calibration_runs = self.values['-C'] |
|
timer = self.values['--timer'] |
|
|
|
print '-' * LINE |
|
print 'PYBENCH %s' % __version__ |
|
print '-' * LINE |
|
print '* using %s %s' % ( |
|
getattr(platform, 'python_implementation', lambda:'Python')(), |
|
string.join(string.split(sys.version), ' ')) |
|
|
|
# Switch off garbage collection |
|
if not withgc: |
|
try: |
|
import gc |
|
except ImportError: |
|
print '* Python version doesn\'t support garbage collection' |
|
else: |
|
try: |
|
gc.disable() |
|
except NotImplementedError: |
|
print '* Python version doesn\'t support gc.disable' |
|
else: |
|
print '* disabled garbage collection' |
|
|
|
# "Disable" sys check interval |
|
if not withsyscheck: |
|
# Too bad the check interval uses an int instead of a long... |
|
value = 2147483647 |
|
try: |
|
sys.setcheckinterval(value) |
|
except (AttributeError, NotImplementedError): |
|
print '* Python version doesn\'t support sys.setcheckinterval' |
|
else: |
|
print '* system check interval set to maximum: %s' % value |
|
|
|
if timer == TIMER_SYSTIMES_PROCESSTIME: |
|
import systimes |
|
print '* using timer: systimes.processtime (%s)' % \ |
|
systimes.SYSTIMES_IMPLEMENTATION |
|
else: |
|
print '* using timer: %s' % timer |
|
|
|
print |
|
|
|
if compare_to: |
|
try: |
|
f = open(compare_to,'rb') |
|
bench = pickle.load(f) |
|
bench.name = compare_to |
|
f.close() |
|
compare_to = bench |
|
except IOError, reason: |
|
print '* Error opening/reading file %s: %s' % ( |
|
repr(compare_to), |
|
reason) |
|
compare_to = None |
|
|
|
if show_bench: |
|
try: |
|
f = open(show_bench,'rb') |
|
bench = pickle.load(f) |
|
bench.name = show_bench |
|
f.close() |
|
bench.print_header() |
|
if compare_to: |
|
bench.print_comparison(compare_to, |
|
hidenoise=hidenoise, |
|
limitnames=limitnames) |
|
else: |
|
bench.print_benchmark(hidenoise=hidenoise, |
|
limitnames=limitnames) |
|
except IOError, reason: |
|
print '* Error opening/reading file %s: %s' % ( |
|
repr(show_bench), |
|
reason) |
|
print |
|
return |
|
|
|
if reportfile: |
|
print 'Creating benchmark: %s (rounds=%i, warp=%i)' % \ |
|
(reportfile, rounds, warp) |
|
print |
|
|
|
# Create benchmark object |
|
bench = Benchmark(reportfile, |
|
verbose=verbose, |
|
timer=timer, |
|
warp=warp, |
|
calibration_runs=calibration_runs) |
|
bench.rounds = rounds |
|
bench.load_tests(Setup, limitnames=limitnames) |
|
try: |
|
bench.calibrate() |
|
bench.run() |
|
except KeyboardInterrupt: |
|
print |
|
print '*** KeyboardInterrupt -- Aborting' |
|
print |
|
return |
|
bench.print_header() |
|
if compare_to: |
|
bench.print_comparison(compare_to, |
|
hidenoise=hidenoise, |
|
limitnames=limitnames) |
|
else: |
|
bench.print_benchmark(hidenoise=hidenoise, |
|
limitnames=limitnames) |
|
|
|
# Ring bell |
|
sys.stderr.write('\007') |
|
|
|
if reportfile: |
|
try: |
|
f = open(reportfile,'wb') |
|
bench.name = reportfile |
|
pickle.dump(bench,f) |
|
f.close() |
|
except IOError, reason: |
|
print '* Error opening/writing reportfile' |
|
except IOError, reason: |
|
print '* Error opening/writing reportfile %s: %s' % ( |
|
reportfile, |
|
reason) |
|
print |
|
|
|
if __name__ == '__main__': |
|
PyBenchCmdline() |