@@ -104,6 +104,19 @@ def pairwise_slopes(values, cp):
104
104
return [abs (float (values [i + 1 ] - values [i ]) / float (cp [i + 1 ] - cp [i ])) for i in range (len (values )- 1 )]
105
105
106
106
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
+
107
120
def detect_warmup (values , cp_threshold = 0.03 , stability_slope_grade = 0.01 ):
108
121
"""
109
122
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):
122
135
cp = cusum (values , threshold = cp_threshold )
123
136
rolling_avg = [avg (values [i :]) for i in cp ]
124
137
138
+ def warmup (cp_index ):
139
+ val_idx = cp [cp_index ] + 1
140
+ return val_idx if val_idx < len (values ) else - 1
141
+
125
142
# find the point where the duration avg is below the cp threshold
126
143
for i , d in enumerate (rolling_avg ):
127
144
if d <= cp_threshold :
128
- return cp [ i ] + 1
145
+ return warmup ( i )
129
146
130
147
# 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 )
134
150
135
151
for i , d in enumerate (slopes ):
136
152
if d <= stability_slope_grade :
137
- return cp [ i ] + 1
153
+ return warmup ( i )
138
154
139
155
return - 1
140
156
except Exception as e :
@@ -176,7 +192,14 @@ def _as_int(value):
176
192
177
193
178
194
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
+
180
203
if bench_args is None :
181
204
bench_args = []
182
205
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):
185
208
_iterations = _as_int (iterations )
186
209
self ._run_once = _iterations <= 1
187
210
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
192
213
193
214
@staticmethod
194
215
def get_bench_module (bench_file ):
@@ -226,9 +247,10 @@ def _call_attr(self, attr_name, *args):
226
247
227
248
def run (self ):
228
249
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__ )
230
251
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 ))
232
254
233
255
# process the args if the processor function is defined
234
256
args = self ._call_attr (ATTR_PROCESS_ARGS , * self .bench_args )
@@ -246,9 +268,9 @@ def run(self):
246
268
bench_func = self ._get_attr (ATTR_BENCHMARK )
247
269
durations = []
248
270
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 ):
252
274
bench_func (* args )
253
275
254
276
for iteration in range (self .iterations ):
@@ -260,28 +282,37 @@ def run(self):
260
282
if self ._run_once :
261
283
print ("@@@ name=%s, duration=%s" % (self .bench_module .__name__ , duration_str ))
262
284
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 ))
264
287
265
288
print (_HRULE )
266
289
print ("### teardown ... " )
267
290
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 )
269
299
print ("### benchmark complete" )
270
300
print (_HRULE )
271
301
print ("### BEST duration: %.3f s" % min (durations ))
272
302
print ("### WORST duration: %.3f s" % max (durations ))
273
303
print ("### AVG (all runs) duration: %.3f s" % (sum (durations ) / len (durations )))
274
304
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 ) )
276
306
no_warmup_durations = durations [warmup_iter :]
277
307
print ("### AVG (no warmup) duration: %.3f s" % (sum (no_warmup_durations ) / len (no_warmup_durations )))
278
308
else :
279
- print ("### WARMUP could not be detected" )
309
+ print ("### WARMUP iteration not specified or could not be detected" )
280
310
print (_HRULE )
281
311
282
312
283
313
def run_benchmark (args ):
284
- warmup = 0
314
+ warmup = - 1
315
+ warmup_runs = 0
285
316
iterations = 1
286
317
bench_file = None
287
318
bench_args = []
@@ -302,6 +333,12 @@ def run_benchmark(args):
302
333
elif arg .startswith ("--warmup" ):
303
334
warmup = _as_int (arg .split ("=" )[1 ])
304
335
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
+
305
342
elif arg == '-p' :
306
343
i += 1
307
344
paths = args [i ].split ("," )
@@ -323,7 +360,7 @@ def run_benchmark(args):
323
360
else :
324
361
print ("### no extra module search paths specified" )
325
362
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 ()
327
364
328
365
329
366
if __name__ == '__main__' :
0 commit comments