|
| 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