Skip to content

Commit cd97248

Browse files
authored
Merge pull request #8635 from huonw/exp-scale-test
[scale-test] Diagnose exponential growth explicitly.
2 parents e800823 + 3f712ad commit cd97248

File tree

2 files changed

+74
-15
lines changed

2 files changed

+74
-15
lines changed

utils/scale-test

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ from __future__ import print_function
1616

1717
import argparse
1818
import json
19+
import math
1920
import os
2021
import os.path
2122
import shutil
@@ -162,51 +163,104 @@ def run_many(args):
162163
return (rng, [run_once(args, ast, [r]) for r in rng])
163164

164165

166+
def is_small(x):
167+
return abs(x) < 1e-9
168+
169+
165170
def linear_regression(x, y):
166171
# By the book: https://en.wikipedia.org/wiki/Simple_linear_regression
167-
n = len(x)
172+
n = float(len(x))
168173
assert n == len(y)
169174
if n == 0:
170175
return 0, 0
171176
sum_x = sum(x)
172177
sum_y = sum(y)
173178
sum_prod = sum(a * b for a, b in zip(x, y))
174179
sum_x_sq = sum(a ** 2 for a in x)
180+
sum_y_sq = sum(b ** 2 for b in y)
175181
mean_x = sum_x / n
176182
mean_y = sum_y / n
177183
mean_prod = sum_prod / n
178184
mean_x_sq = sum_x_sq / n
185+
mean_y_sq = sum_y_sq / n
179186
covar_xy = mean_prod - mean_x * mean_y
180187
var_x = mean_x_sq - mean_x**2
188+
var_y = mean_y_sq - mean_y**2
181189
slope = covar_xy / var_x
182190
inter = mean_y - slope * mean_x
183-
return slope, inter
191+
192+
# Compute the correlation coefficient aka r^2, to compare goodness-of-fit.
193+
if is_small(var_y):
194+
# all of the outputs are the same, so this is a perfect fit
195+
assert is_small(covar_xy)
196+
cor_coeff_sq = 1.0
197+
elif is_small(var_x):
198+
# all of the inputs are the same, and the outputs are different, so
199+
# this is a completely imperfect fit
200+
assert is_small(covar_xy)
201+
cor_coeff_sq = 0.0
202+
else:
203+
cor_coeff_sq = covar_xy**2 / (var_x * var_y)
204+
205+
return slope, inter, cor_coeff_sq
206+
207+
208+
# Y = a * X^b, returns a, b, R^2
209+
def fit_polynomial_model(x, y):
210+
# transform into linear regression via log(Y) = b*log(X) + log(a)
211+
log_x = [math.log(val) for val in x]
212+
log_y = [math.log(val) for val in y]
213+
214+
b, log_a, r2 = linear_regression(log_x, log_y)
215+
return b, math.exp(log_a), r2
216+
217+
218+
# Y = a * b^X, returns a, b, R^2
219+
def fit_exponential_model(x, y):
220+
# transform into linear regression via log(Y) = log(b) * X + log(a)
221+
log_y = [math.log(val) for val in y]
222+
223+
log_b, log_a, r2 = linear_regression(x, log_y)
224+
return math.exp(log_b), math.exp(log_a), r2
184225

185226

186227
def report(args, rng, runs):
187-
import math
188228
bad = False
189229
keys = set.intersection(*[set(j.keys()) for j in runs])
190230
if len(keys) == 0:
191231
print("No data found")
192232
if len(args.select) != 0:
193233
"(perhaps try a different --select?)"
194234
return True
195-
x = [math.log(n) for n in rng]
196235
rows = []
197236
for k in keys:
198237
vals = [r[k] for r in runs]
199238
bounded = [max(v, 1) for v in vals]
200-
y = [math.log(b) for b in bounded]
201-
b, a = linear_regression(x, y)
202-
b = 0 if abs(b) < 1e-9 else b
203-
rows.append((b, k, vals))
239+
p_b, p_a, p_r2 = fit_polynomial_model(rng, bounded)
240+
e_b, e_a, e_r2 = fit_exponential_model(rng, bounded)
241+
if p_r2 >= e_r2:
242+
# polynomial is best
243+
p_b = 0 if is_small(p_b) else p_b
244+
rows.append((False, p_b, k, vals))
245+
else:
246+
# exponential is best
247+
rows.append((True, e_b, k, vals))
248+
# Exponential fits always go after polynomial fits.
204249
rows.sort()
205-
for (b, k, vals) in rows:
206-
if b >= args.threshold:
250+
for (is_exp, b, k, vals) in rows:
251+
# same threshold for both the polynomial exponent or the exponential
252+
# base.
253+
if is_exp:
254+
this_is_bad = b >= args.exponential_threshold
255+
formatted = '%1.1f^n' % b
256+
else:
257+
this_is_bad = b >= args.polynomial_threshold
258+
formatted = 'n^%1.1f' % b
259+
260+
if this_is_bad:
207261
bad = True
208-
if not args.quiet or b >= args.threshold:
209-
print("O(n^%1.1f) : %s" % (b, k))
262+
if not args.quiet or this_is_bad:
263+
print("O(%s) : %s" % (formatted, k))
210264
if args.values:
211265
print(" = ", vals)
212266
return bad
@@ -228,8 +282,13 @@ def main():
228282
'--quiet', action='store_true',
229283
default=False, help='only print superlinear stats')
230284
parser.add_argument(
231-
'--threshold', type=float,
232-
default=1.2, help='exponent beyond which to consider "bad scaling"')
285+
'--polynomial-threshold', type=float,
286+
default=1.2,
287+
help='minimum exponent for polynomial fit to consider "bad scaling"')
288+
parser.add_argument(
289+
'--exponential-threshold', type=float,
290+
default=1.2,
291+
help='minimum base for exponential fit to consider "bad scaling"')
233292
parser.add_argument(
234293
'-typecheck', '--typecheck', action='store_true',
235294
default=False, help='only run compiler with -typecheck')

validation-test/compiler_scale/callee_analysis_invalidation.gyb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %scale-test -O --threshold 0.2 --begin 20 --end 25 --step 1 --select computeMethodCallees %s
1+
// RUN: %scale-test -O --polynomial-threshold 0.2 --begin 20 --end 25 --step 1 --select computeMethodCallees %s
22
// REQUIRES: OS=macosx
33
// REQUIRES: asserts
44

0 commit comments

Comments
 (0)