Skip to content

Commit 1db8cb0

Browse files
committed
harness: add -w and -r flags
- -r allows for a number of pre-measurement runs to be performed (to warmup the benchmark) - -w specified where the warmup can occur in a specified benchmark, if it is not set an attempt to detect the warmup point is made. The warmup detection is based on several heuristics and therefore not 100% reliable
1 parent 6a0eca6 commit 1db8cb0

File tree

1 file changed

+58
-21
lines changed

1 file changed

+58
-21
lines changed

graalpython/benchmarks/src/harness.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,19 @@ def pairwise_slopes(values, cp):
104104
return [abs(float(values[i+1] - values[i]) / float(cp[i+1] - cp[i])) for i in range(len(values)-1)]
105105

106106

107+
def last_n_percent_runs(values, n=0.1):
108+
assert 0.0 < n <= 1.0
109+
end_runs_idx = len(values) - int(len(values) * n)
110+
end_runs_idx = len(values) - 1 if end_runs_idx >= len(values) else end_runs_idx
111+
return values[end_runs_idx:], list(range(end_runs_idx, len(values)))
112+
113+
114+
def first_n_percent_runs(values, n=0.1):
115+
assert 0.0 < n <= 1.0
116+
first_run_idx = int(len(values) * n)
117+
return first_run_idx -1 if first_run_idx == len(values) else first_run_idx
118+
119+
107120
def detect_warmup(values, cp_threshold=0.03, stability_slope_grade=0.01):
108121
"""
109122
detect the point of warmup point (iteration / run)
@@ -122,19 +135,22 @@ def detect_warmup(values, cp_threshold=0.03, stability_slope_grade=0.01):
122135
cp = cusum(values, threshold=cp_threshold)
123136
rolling_avg = [avg(values[i:]) for i in cp]
124137

138+
def warmup(cp_index):
139+
val_idx = cp[cp_index] + 1
140+
return val_idx if val_idx < len(values) else -1
141+
125142
# find the point where the duration avg is below the cp threshold
126143
for i, d in enumerate(rolling_avg):
127144
if d <= cp_threshold:
128-
return cp[i] + 1
145+
return warmup(i)
129146

130147
# could not find something below the CP threshold (noise in the data), use the stabilisation of slopes
131-
end_runs_idx = len(values) - int(len(values) * 0.1)
132-
end_runs_idx = len(values) - 1 if end_runs_idx >= len(values) else end_runs_idx
133-
slopes = pairwise_slopes(rolling_avg + values[end_runs_idx:], cp + list(range(end_runs_idx, len(values))))
148+
last_n_vals, last_n_idx = last_n_percent_runs(values, 0.1)
149+
slopes = pairwise_slopes(rolling_avg + last_n_vals, cp + last_n_idx)
134150

135151
for i, d in enumerate(slopes):
136152
if d <= stability_slope_grade:
137-
return cp[i] + 1
153+
return warmup(i)
138154

139155
return -1
140156
except Exception as e:
@@ -176,7 +192,14 @@ def _as_int(value):
176192

177193

178194
class BenchRunner(object):
179-
def __init__(self, bench_file, bench_args=None, iterations=1, warmup=0):
195+
def __init__(self, bench_file, bench_args=None, iterations=1, warmup=-1, warmup_runs=0):
196+
assert isinstance(iterations, int), \
197+
"BenchRunner iterations argument must be an int, got %s instead" % iterations
198+
assert isinstance(warmup, int), \
199+
"BenchRunner warmup argument must be an int, got %s instead" % warmup
200+
assert isinstance(warmup_runs, int), \
201+
"BenchRunner warmup_runs argument must be an int, got %s instead" % warmup_runs
202+
180203
if bench_args is None:
181204
bench_args = []
182205
self.bench_module = BenchRunner.get_bench_module(bench_file)
@@ -185,10 +208,8 @@ def __init__(self, bench_file, bench_args=None, iterations=1, warmup=0):
185208
_iterations = _as_int(iterations)
186209
self._run_once = _iterations <= 1
187210
self.iterations = 1 if self._run_once else _iterations
188-
189-
assert isinstance(self.iterations, int)
190-
self.warmup = _as_int(warmup)
191-
assert isinstance(self.warmup, int)
211+
self.warmup_runs = warmup_runs if warmup_runs > 0 else 0
212+
self.warmup = warmup if warmup > 0 else -1
192213

193214
@staticmethod
194215
def get_bench_module(bench_file):
@@ -226,9 +247,10 @@ def _call_attr(self, attr_name, *args):
226247

227248
def run(self):
228249
if self._run_once:
229-
print("### %s, exactly one iteration (no warmup curves)" % (self.bench_module.__name__))
250+
print("### %s, exactly one iteration (no warmup curves)" % self.bench_module.__name__)
230251
else:
231-
print("### %s, %s warmup iterations, %s bench iterations " % (self.bench_module.__name__, self.warmup, self.iterations))
252+
print("### %s, %s warmup iterations, %s bench iterations " % (self.bench_module.__name__,
253+
self.warmup_runs, self.iterations))
232254

233255
# process the args if the processor function is defined
234256
args = self._call_attr(ATTR_PROCESS_ARGS, *self.bench_args)
@@ -246,9 +268,9 @@ def run(self):
246268
bench_func = self._get_attr(ATTR_BENCHMARK)
247269
durations = []
248270
if bench_func and hasattr(bench_func, '__call__'):
249-
if self.warmup:
250-
print("### warming up for %s iterations ... " % self.warmup)
251-
for _ in range(self.warmup):
271+
if self.warmup_runs:
272+
print("### (pre)warming up for %s iterations ... " % self.warmup_runs)
273+
for _ in range(self.warmup_runs):
252274
bench_func(*args)
253275

254276
for iteration in range(self.iterations):
@@ -260,28 +282,37 @@ def run(self):
260282
if self._run_once:
261283
print("@@@ name=%s, duration=%s" % (self.bench_module.__name__, duration_str))
262284
else:
263-
print("### iteration=%s, name=%s, duration=%s" % (iteration, self.bench_module.__name__, duration_str))
285+
print("### iteration=%s, name=%s, duration=%s" % (iteration, self.bench_module.__name__,
286+
duration_str))
264287

265288
print(_HRULE)
266289
print("### teardown ... ")
267290
self._call_attr(ATTR_TEARDOWN)
268-
warmup_iter = detect_warmup(durations)
291+
warmup_iter = self.warmup if self.warmup > 0 else detect_warmup(durations)
292+
# if we cannot detect a warmup starting point but we performed some pre runs, we take a starting point
293+
# after the 10% of the first runs ...
294+
if warmup_iter < 0 and self.warmup_runs > 0:
295+
print("### warmup could not be detected, but %s pre-runs were executed.\n"
296+
"### we assume the benchmark is warmed up and pick an iteration "
297+
"in the first 10%% of the runs" % self.warmup_runs)
298+
warmup_iter = first_n_percent_runs(durations, 0.1)
269299
print("### benchmark complete")
270300
print(_HRULE)
271301
print("### BEST duration: %.3f s" % min(durations))
272302
print("### WORST duration: %.3f s" % max(durations))
273303
print("### AVG (all runs) duration: %.3f s" % (sum(durations) / len(durations)))
274304
if warmup_iter > 0:
275-
print("### WARMUP detected at iteration: %d" % warmup_iter)
305+
print("### WARMUP %s at iteration: %d" % ("specified" if self.warmup > 0 else "detected", warmup_iter))
276306
no_warmup_durations = durations[warmup_iter:]
277307
print("### AVG (no warmup) duration: %.3f s" % (sum(no_warmup_durations) / len(no_warmup_durations)))
278308
else:
279-
print("### WARMUP could not be detected")
309+
print("### WARMUP iteration not specified or could not be detected")
280310
print(_HRULE)
281311

282312

283313
def run_benchmark(args):
284-
warmup = 0
314+
warmup = -1
315+
warmup_runs = 0
285316
iterations = 1
286317
bench_file = None
287318
bench_args = []
@@ -302,6 +333,12 @@ def run_benchmark(args):
302333
elif arg.startswith("--warmup"):
303334
warmup = _as_int(arg.split("=")[1])
304335

336+
elif arg == '-r':
337+
i += 1
338+
warmup_runs = _as_int(args[i])
339+
elif arg.startswith("--warmup-runs"):
340+
warmup_runs = _as_int(arg.split("=")[1])
341+
305342
elif arg == '-p':
306343
i += 1
307344
paths = args[i].split(",")
@@ -323,7 +360,7 @@ def run_benchmark(args):
323360
else:
324361
print("### no extra module search paths specified")
325362

326-
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup).run()
363+
BenchRunner(bench_file, bench_args=bench_args, iterations=iterations, warmup=warmup, warmup_runs=warmup_runs).run()
327364

328365

329366
if __name__ == '__main__':

0 commit comments

Comments
 (0)