Skip to content

Commit 6f19ad7

Browse files
authored
Merge pull request #5139 from graydon/scale-tester
2 parents 93fde73 + 9fa3f49 commit 6f19ad7

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

utils/scale-test

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python
2+
#
3+
# -*- python -*-
4+
#
5+
# Runs a .gyb scale-testing file repeatedly through swiftc while varying a
6+
# scaling variable 'N', collects json stats from the compiler, transforms the
7+
# problem to log-space and runs a linear regression to estimate the exponent on
8+
# the stat's growth curve relative to N.
9+
#
10+
# The estimate will be more accurate as N increases, so if you get a
11+
# not-terribly-convincing estimate, try increasing --begin and --end to larger
12+
# values.
13+
#
14+
15+
import gyb, os, os.path, subprocess
16+
17+
def find_which(p):
18+
for d in os.environ["PATH"].split(os.pathsep):
19+
full = os.path.join(d,p)
20+
if os.path.isfile(full) and os.access(full, os.X_OK):
21+
return full
22+
return p
23+
24+
# Evidently the debug-symbol reader in dtrace is sufficiently slow and/or buggy
25+
# that attempting to inject probes into a binary w/ debuginfo is asking for a
26+
# failed run (possibly racing with probe insertion, or probing the stabs
27+
# entries, see rdar://problem/7037927 or rdar://problem/11490861 respectively),
28+
# so we sniff the presence of debug symbols here.
29+
def has_debuginfo(swiftc):
30+
swiftc = find_which(swiftc)
31+
for line in subprocess.check_output(["dwarfdump", "--file-stats", swiftc]).splitlines():
32+
if '%' not in line:
33+
continue
34+
fields = line.split()
35+
if fields[8] != '0.00%' or fields[10] != '0.00%':
36+
return True
37+
return False
38+
39+
40+
def write_input_file(args, ast, d, n):
41+
ifile = os.path.join(d, "in%d.swift" % n)
42+
with open(ifile,'w+') as f:
43+
f.write(gyb.execute_template(ast, '', N=n))
44+
return ifile
45+
46+
47+
def run_once(args, ast, rng):
48+
import sys, shutil, tempfile, json
49+
r = {}
50+
try:
51+
d = tempfile.mkdtemp()
52+
inputs = [write_input_file(args, ast, d, i) for i in rng]
53+
primary = inputs[-1]
54+
ofile = os.path.join(d, "out.o")
55+
mode = "-c"
56+
if args.parse:
57+
mode = "-parse"
58+
command = [args.swiftc_binary,
59+
"-frontend", mode,
60+
"-o", ofile,
61+
"-primary-file", primary] + inputs
62+
if args.dtrace:
63+
trace = os.path.join(d, "trace.txt")
64+
script = "pid$target:swiftc:*%s*:entry { @[probefunc] = count() }" % args.select
65+
subprocess.check_call(
66+
["sudo", "dtrace", "-q",
67+
"-o", trace,
68+
"-b", "256",
69+
"-n", script,
70+
"-c", " ".join(command)])
71+
r = {fields[0]: int(fields[1]) for fields in
72+
[line.split() for line in open(trace)]
73+
if len(fields) == 2}
74+
else:
75+
stats = os.path.join(d, "stats.json")
76+
subprocess.check_call(
77+
command + ["-Xllvm", "-stats",
78+
"-Xllvm", "-stats-json",
79+
"-Xllvm", "-info-output-file=" + stats])
80+
with open(stats) as f:
81+
r = json.load(f)
82+
finally:
83+
shutil.rmtree(d)
84+
85+
return {k:v for (k,v) in r.items() if args.select in k}
86+
87+
88+
def run_many(args):
89+
90+
if args.dtrace and has_debuginfo(args.swiftc_binary):
91+
print ""
92+
print "**************************************************"
93+
print ""
94+
print "dtrace is unreliable on binaries w/ debug symbols"
95+
print "please run 'strip -S %s'" % args.swiftc_binary
96+
print "or pass a different --swiftc-binary"
97+
print ""
98+
print "**************************************************"
99+
print ""
100+
exit(1)
101+
102+
ast = gyb.parse_template(args.file.name, args.file.read())
103+
rng = range(args.begin, args.end, args.step)
104+
if args.multi_file:
105+
return (rng, [run_once(args, ast, rng[0:i+1]) for i in range(len(rng))])
106+
else:
107+
return (rng, [run_once(args, ast, [r]) for r in rng])
108+
109+
110+
def report(args, rng, runs):
111+
import numpy as np
112+
bad = False
113+
keys = set.intersection(*[set(j.keys()) for j in runs])
114+
A = np.vstack([np.log(rng), np.ones(len(rng))]).T
115+
rows = []
116+
for k in keys:
117+
vals = [r[k] for r in runs]
118+
bounded = [max(v, 1) for v in vals]
119+
b, a = np.linalg.lstsq(A, np.log(bounded))[0]
120+
b = 0 if np.isclose(b, 0) else b
121+
rows.append((b, k, vals))
122+
rows.sort()
123+
tolerance = 1.2
124+
for (b, k, vals) in rows:
125+
if b >= tolerance:
126+
bad = True
127+
if not args.quiet or b >= tolerance:
128+
print "O(n^%1.1f) : %s" % (b, k)
129+
if args.values:
130+
print " = ", vals
131+
return bad
132+
133+
134+
def main():
135+
import argparse, sys
136+
parser = argparse.ArgumentParser()
137+
parser.add_argument(
138+
'file', type=argparse.FileType(),
139+
help='Path to GYB template file (defaults to stdin)', nargs='?',
140+
default=sys.stdin)
141+
parser.add_argument(
142+
'--values', action='store_true',
143+
default=False, help='print stat values')
144+
parser.add_argument(
145+
'--quiet', action='store_true',
146+
default=False, help='only print superlinear stats')
147+
parser.add_argument(
148+
'--parse', action='store_true',
149+
default=False, help='only run compiler with -parse')
150+
parser.add_argument(
151+
'--dtrace', action='store_true',
152+
default=False, help='use dtrace to sample all functions')
153+
parser.add_argument(
154+
'--multi-file', action='store_true',
155+
default=False, help='vary number of input files as well')
156+
parser.add_argument(
157+
'--begin', type=int,
158+
default=10, help='first value for N')
159+
parser.add_argument(
160+
'--end', type=int,
161+
default=100, help='last value for N')
162+
parser.add_argument(
163+
'--step', type=int,
164+
default=10, help='step value for N')
165+
parser.add_argument(
166+
'--swiftc-binary',
167+
default="swiftc", help='swift binary to execute')
168+
parser.add_argument(
169+
'--select',
170+
default="", help='substring of counters/symbols to restrict attention to')
171+
172+
args = parser.parse_args(sys.argv[1:])
173+
(rng, runs) = run_many(args)
174+
if report(args, rng, runs):
175+
exit(1)
176+
exit(0)
177+
178+
if __name__ == '__main__':
179+
main()

0 commit comments

Comments
 (0)