~linaro-toolchain-dev/cortex-strings/trunk

« back to all changes in this revision

Viewing changes to scripts/bench.py

  • Committer: Will Newton
  • Date: 2013-06-25 14:07:04 UTC
  • Revision ID: will.newton@linaro.org-20130625140704-jp1ad8y2p8d416qk
Support multiple runs of each benchmark.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
"""Simple harness that benchmarks different variants of the routines,
 
4
caches the results, and emits all of the records at the end.
 
5
 
 
6
Results are generated for different values of:
 
7
 * Source
 
8
 * Routine
 
9
 * Length
 
10
 * Alignment
 
11
"""
 
12
 
 
13
import subprocess
 
14
import math
 
15
import sys
 
16
 
 
17
# Prefix to the executables
 
18
build = '../build/try-'
 
19
 
 
20
ALL = 'memchr memcmp memcpy memset strchr strcmp strcpy strlen'
 
21
 
 
22
HAS = {
 
23
    'this': 'bounce memchr memcpy memset strchr strcpy strlen',
 
24
    'bionic-a9': 'memcmp memcpy memset strcmp strcpy strlen',
 
25
    'bionic-a15': 'memcmp memcpy memset strcmp strcpy strlen',
 
26
    'bionic-c': ALL,
 
27
    'csl': 'memcpy memset',
 
28
    'glibc': 'memcpy memset strlen',
 
29
    'glibc-c': ALL,
 
30
    'newlib': 'memcpy strcmp strcpy strlen',
 
31
    'newlib-c': ALL,
 
32
    'newlib-xscale': 'memchr memcpy memset strchr strcmp strcpy strlen',
 
33
    'plain': 'memset memcpy strcmp strcpy',
 
34
}
 
35
 
 
36
BOUNCE_ALIGNMENTS = ['1']
 
37
SINGLE_BUFFER_ALIGNMENTS = ['1', '2', '4', '8', '16', '32']
 
38
DUAL_BUFFER_ALIGNMENTS = ['1:32', '2:32', '4:32', '8:32', '16:32', '32:32']
 
39
 
 
40
ALIGNMENTS = {
 
41
    'bounce': BOUNCE_ALIGNMENTS,
 
42
    'memchr': SINGLE_BUFFER_ALIGNMENTS,
 
43
    'memset': SINGLE_BUFFER_ALIGNMENTS,
 
44
    'strchr': SINGLE_BUFFER_ALIGNMENTS,
 
45
    'strlen': SINGLE_BUFFER_ALIGNMENTS,
 
46
    'memcmp': DUAL_BUFFER_ALIGNMENTS,
 
47
    'memcpy': DUAL_BUFFER_ALIGNMENTS,
 
48
    'strcmp': DUAL_BUFFER_ALIGNMENTS,
 
49
    'strcpy': DUAL_BUFFER_ALIGNMENTS,
 
50
}
 
51
 
 
52
NUM_RUNS = 5
 
53
 
 
54
def run(cache, variant, function, bytes, loops, alignment, run_id, quiet=False):
 
55
    """Perform a single run, exercising the cache as appropriate."""
 
56
    key = ':'.join('%s' % x for x in (variant, function, bytes, loops, alignment, run_id))
 
57
 
 
58
    if key in cache:
 
59
        got = cache[key]
 
60
    else:
 
61
        xbuild = build
 
62
        cmd = '%(xbuild)s%(variant)s -t %(function)s -c %(bytes)s -l %(loops)s -a %(alignment)s -r %(run_id)s' % locals()
 
63
 
 
64
        try:
 
65
            got = subprocess.check_output(cmd.split()).strip()
 
66
        except OSError, ex:
 
67
            assert False, 'Error %s while running %s' % (ex, cmd)
 
68
 
 
69
    parts = got.split(':')
 
70
    took = float(parts[7])
 
71
 
 
72
    cache[key] = got
 
73
 
 
74
    if not quiet:
 
75
        print got
 
76
        sys.stdout.flush()
 
77
 
 
78
    return took
 
79
 
 
80
def run_many(cache, variants, bytes, all_functions):
 
81
    # We want the data to come out in a useful order.  So fix an
 
82
    # alignment and function, and do all sizes for a variant first
 
83
    bytes = sorted(bytes)
 
84
    mid = bytes[int(len(bytes)/1.5)]
 
85
 
 
86
    if not all_functions:
 
87
        # Use the ordering in 'this' as the default
 
88
        all_functions = HAS['this'].split()
 
89
 
 
90
        # Find all other functions
 
91
        for functions in HAS.values():
 
92
            for function in functions.split():
 
93
                if function not in all_functions:
 
94
                    all_functions.append(function)
 
95
 
 
96
    for function in all_functions:
 
97
        for alignment in ALIGNMENTS[function]:
 
98
            for variant in variants:
 
99
                if function not in HAS[variant].split():
 
100
                    continue
 
101
 
 
102
                # Run a tracer through and see how long it takes and
 
103
                # adjust the number of loops based on that.  Not great
 
104
                # for memchr() and similar which are O(n), but it will
 
105
                # do
 
106
                f = 50000000
 
107
                want = 5.0
 
108
 
 
109
                loops = int(f / math.sqrt(max(1, mid)))
 
110
                took = run(cache, variant, function, mid, loops, alignment, 0,
 
111
                           quiet=True)
 
112
                # Keep it reasonable for silly routines like bounce
 
113
                factor = min(20, max(0.05, want/took))
 
114
                f = f * factor
 
115
                
 
116
                # Round f to a few significant figures
 
117
                scale = 10**int(math.log10(f) - 1)
 
118
                f = scale*int(f/scale)
 
119
 
 
120
                for b in sorted(bytes):
 
121
                    # Figure out the number of loops to give a roughly consistent run
 
122
                    loops = int(f / math.sqrt(max(1, b)))
 
123
                    for run_id in range(0, NUM_RUNS):
 
124
                        run(cache, variant, function, b, loops, alignment,
 
125
                            run_id)
 
126
 
 
127
def run_top(cache):
 
128
    variants = sorted(HAS.keys())
 
129
    functions = sys.argv[1:]
 
130
 
 
131
    # Upper limit in bytes to test to
 
132
    top = 512*1024
 
133
    # Test all powers of 2
 
134
    step1 = 2.0
 
135
    # Test intermediate powers of 1.4
 
136
    step2 = 1.4
 
137
    
 
138
    bytes = []
 
139
    
 
140
    for step in [step1, step2]:
 
141
        if step:
 
142
            # Figure out how many steps get us up to the top
 
143
            steps = int(round(math.log(top) / math.log(step)))
 
144
            bytes.extend([int(step**x) for x in range(0, steps+1)])
 
145
 
 
146
    run_many(cache, variants, bytes, functions)
 
147
 
 
148
def main():
 
149
    cachename = 'cache.txt'
 
150
 
 
151
    cache = {}
 
152
 
 
153
    try:
 
154
        with open(cachename) as f:
 
155
            for line in f:
 
156
                line = line.strip()
 
157
                parts = line.split(':')
 
158
                cache[':'.join(parts[:7])] = line
 
159
    except:
 
160
        pass
 
161
 
 
162
    try:
 
163
        run_top(cache)
 
164
    finally:
 
165
        with open(cachename, 'w') as f:
 
166
            for line in sorted(cache.values()):
 
167
                print >> f, line
 
168
 
 
169
if __name__ == '__main__':
 
170
    main()