summaryrefslogtreecommitdiff
path: root/bench/bench.py
blob: aad0fb4f654ca9f92cdd53fd95e4b366f4a13ea0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env python
#
# __COPYRIGHT__
#
# A script for timing snippets of Python code.
#
# By default, this script will execute a single Python file specified on
# the command line and time any functions in a list named "FunctionList"
# set by the Python file under test, or (by default) time any functions
# in the file whose names begin with "Func".
#
# All functions are assumed to get passed the same arguments, and the
# inputs are specified in a list named "Data," each element of which
# is a list consisting of a tag name, a list of positional arguments,
# and a dictionary of keyword arguments.
#
# Each function is expected to test a single, comparable snippet of
# of Python code.  IMPORTANT:  We want to test the timing of the code
# itself, not Python function call overhead, so every function should
# put its code under test within the following block:
#
#       for i in IterationList:
#
# This will allow (as much as possible) us to time just the code itself,
# not Python function call overhead.
from __future__ import division, print_function

import getopt
import sys
import time
import types

Usage = """\
Usage:  bench.py OPTIONS file.py
  --clock                       Use the time.clock function
  --func PREFIX                 Test functions whose names begin with PREFIX
  -h, --help                    Display this help and exit
  -i ITER, --iterations ITER    Run each code snippet ITER times
  --time                        Use the time.time function
  -r RUNS, --runs RUNS          Average times for RUNS invocations of 
"""

# How many times each snippet of code will be (or should be) run by the 
# functions under test to gather the time (the "inner loop").

Iterations = 1000

# How many times we'll run each function to collect its aggregate time
# and try to average out timing differences induced by system performance
# (the "outer loop").

Runs = 10

# The prefix of the functions under test.  This will be used if
# there's no explicit list defined in FunctionList.

FunctionPrefix = 'Func'

# Comment from Python2 timeit module:
# The difference in default timer function is because on Windows,
# clock() has microsecond granularity but time()'s granularity is 1/60th
# of a second; on Unix, clock() has 1/100th of a second granularity and
# time() is much more precise.  On either platform, the default timer
# functions measure wall clock time, not the CPU time.  This means that
# other processes running on the same computer may interfere with the
# timing.  The best thing to do when accurate timing is necessary is to
# repeat the timing a few times and use the best time.  The -i option is
# good for this.
# On Python3, a new time.perf_counter function picks the best available
# timer, so we use that if we can, else fall back as per above.

try:
    Now = time.perf_counter
except AttributeError:
    if sys.platform == 'win32':
        Now = time.clock
    else:
        Now = time.time

opts, args = getopt.getopt(sys.argv[1:], 'hi:r:',
                           ['clock', 'func=', 'help',
                            'iterations=', 'time', 'runs='])

for o, a in opts:
    if o in ['--clock']:
        try:
            Now = time.clock
        except AttributeError:
            sys.stderr.write("time.clock unavailable on this Python\n")
            sys.exit(1)
    elif o in ['--func']:
        FunctionPrefix = a
    elif o in ['-h', '--help']:
        sys.stdout.write(Usage)
        sys.exit(0)
    elif o in ['-i', '--iterations']:
        Iterations = int(a)
    elif o in ['--time']:
        Now = time.time
    elif o in ['-r', '--runs']:
        Runs = int(a)

if len(args) != 1:
    sys.stderr.write("bench.py:  only one file argument must be specified\n")
    sys.stderr.write(Usage)
    sys.exit(1)

with open(args[0], 'r') as f:
    exec(f.read())

try:
    FunctionList
except NameError:
    function_names = sorted([x for x in list(locals().keys()) if x[:4] == FunctionPrefix])
    lvars = locals()
    l = [lvars[f] for f in function_names]
    FunctionList = [f for f in l if isinstance(f, types.FunctionType)]

IterationList = [None] * Iterations

def timer(func, *args, **kw):
    results = []
    for i in range(Runs):
        start = Now()
        func(*args, **kw)
        finish = Now()
        results.append((finish - start) / Iterations)
    return results

def display(label, results):
    total = 0.0
    for r in results:
        total += r
    print("    %8.3f" % ((total * 1e6) / len(results)), ':', label)

for func in FunctionList:
    if func.__doc__: d = ' (' + func.__doc__ + ')'
    else: d = ''
    print(func.__name__ + d + ':')

    for label, args, kw in Data:
        r = timer(func, *args, **kw)
        display(label, r)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4: