Commit | Line | Data |
---|---|---|
e63124bc MW |
1 | /* -*-c-*- |
2 | * | |
3 | * Benchmarking in the test-vector framework | |
4 | * | |
5 | * (c) 2023 Straylight/Edgeware | |
6 | */ | |
7 | ||
8 | /*----- Licensing notice --------------------------------------------------* | |
9 | * | |
10 | * This file is part of the mLib utilities library. | |
11 | * | |
12 | * mLib is free software: you can redistribute it and/or modify it under | |
13 | * the terms of the GNU Library General Public License as published by | |
14 | * the Free Software Foundation; either version 2 of the License, or (at | |
15 | * your option) any later version. | |
16 | * | |
17 | * mLib is distributed in the hope that it will be useful, but WITHOUT | |
18 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
19 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | |
20 | * License for more details. | |
21 | * | |
22 | * You should have received a copy of the GNU Library General Public | |
23 | * License along with mLib. If not, write to the Free Software | |
24 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |
25 | * USA. | |
26 | */ | |
27 | ||
28 | /*----- Header files ------------------------------------------------------*/ | |
29 | ||
30 | #include "bench.h" | |
b1a20bee | 31 | |
e63124bc | 32 | #include "tvec.h" |
b1a20bee MW |
33 | #include "tvec-bench.h" |
34 | #include "tvec-output.h" | |
35 | #include "tvec-types.h" | |
e63124bc MW |
36 | |
37 | /*----- Data structures ---------------------------------------------------*/ | |
38 | ||
39 | struct benchrun { | |
67b5031e MW |
40 | struct tvec_state *tv; /* test vector state */ |
41 | const struct tvec_env *env; /* subordinate environment */ | |
42 | void *ctx; /* subordinate env's context */ | |
43 | unsigned long *n; /* iteration count address */ | |
44 | const struct tvec_reg *in; struct tvec_reg *out; /* register vectors */ | |
45 | tvec_testfn *fn; /* test function to run */ | |
e63124bc MW |
46 | }; |
47 | ||
48 | /*----- Global variables --------------------------------------------------*/ | |
49 | ||
67b5031e | 50 | struct bench_state *tvec_benchstate; /* common benchmarking state */ |
e63124bc | 51 | |
b1a20bee | 52 | /*----- Output utilities --------------------------------------------------*/ |
67b5031e | 53 | |
b1a20bee | 54 | /* --- @tvec_benchreport@ --- * |
67b5031e | 55 | * |
b1a20bee MW |
56 | * Arguments: @const struct gprintf_ops *gops@ = print operations |
57 | * @void *go@ = print destination | |
58 | * @unsigned unit@ = the unit being measured (@TVBU_...@) | |
59 | * @unsigned style@ = output style (@TVSF_...@) | |
60 | * @const struct bench_timing *t@ = the benchmark result | |
67b5031e MW |
61 | * |
62 | * Returns: --- | |
63 | * | |
b1a20bee MW |
64 | * Use: Formats a report about the benchmark performance. This |
65 | * function is intended to be called on by an output | |
66 | * @ebench@ function. | |
67b5031e | 67 | */ |
e63124bc | 68 | |
b1a20bee MW |
69 | void tvec_benchreport(const struct gprintf_ops *gops, void *go, |
70 | unsigned unit, unsigned style, | |
71 | const struct bench_timing *t) | |
e63124bc | 72 | { |
b1a20bee MW |
73 | if (!t) { |
74 | if (style&TVSF_RAW) gprintf(gops, go, "FAILED"); | |
75 | else gprintf(gops, go, "benchmark FAILED"); | |
76 | } else if (!(style&TVSF_RAW)) | |
77 | bench_report(gops, go, unit, t); | |
78 | else { | |
79 | switch (unit) { | |
80 | case BTU_OP: gprintf(gops, go, "ops=%.0f", t->n); break; | |
81 | case BTU_BYTE: gprintf(gops, go, "bytes=%.0f", t->n); break; | |
82 | default: assert(0); | |
83 | } | |
84 | gprintf(gops, go, " sec=%.6g", t->t); | |
85 | if (t->f&BTF_CYOK) gprintf(gops, go, " cy=%.0f", t->cy); | |
86 | } | |
e63124bc MW |
87 | } |
88 | ||
b1a20bee MW |
89 | /*----- Default output implementation -------------------------------------*/ |
90 | ||
91 | /* --- @fallback_bbench@ --- * | |
c81c35df | 92 | * |
b1a20bee MW |
93 | * Arguments: @struct tvec_output *o@ = output sink, secretly a |
94 | * @struct fallback_output@ | |
95 | * @const char *desc@ = adhoc test description | |
96 | * @unsigned unit@ = measurement unit (@TVBU_...@) | |
c81c35df MW |
97 | * |
98 | * Returns: --- | |
99 | * | |
b1a20bee MW |
100 | * Use: Report that a benchmark has started. |
101 | * | |
102 | * The fallback implementation does nothing here. All of the | |
103 | * reporting happens in @fallback_ebench@. | |
104 | */ | |
105 | ||
106 | static void fallback_bbench(struct tvec_output *o, | |
107 | const char *desc, unsigned unit) | |
108 | { ; } | |
109 | ||
110 | /* --- @fallback_ebench@ --- * | |
c81c35df | 111 | * |
b1a20bee MW |
112 | * Arguments: @struct tvec_output *o@ = output sink, secretly a |
113 | * @struct fallback_output@ | |
114 | * @const char *desc@ = adhoc test description | |
115 | * @unsigned unit@ = measurement unit (@BTU_...@) | |
116 | * @const struct bench_timing *t@ = measurement | |
c81c35df | 117 | * |
b1a20bee MW |
118 | * Returns: --- |
119 | * | |
120 | * Use: Report a benchmark's results | |
121 | * | |
122 | * The fallback implementation just delegates to the default | |
123 | * benchmark reporting to produce a line written through the | |
124 | * standard @report@ output operation. | |
c81c35df MW |
125 | */ |
126 | ||
b1a20bee MW |
127 | static void fallback_ebench(struct tvec_output *o, |
128 | const char *desc, unsigned unit, | |
129 | const struct bench_timing *t) | |
c81c35df | 130 | { |
b1a20bee MW |
131 | struct tvec_fallbackoutput *fo = (struct tvec_fallbackoutput *)o; |
132 | struct tvec_state *tv = fo->tv; | |
133 | const struct tvec_regdef *rd; | |
134 | dstr d = DSTR_INIT; | |
135 | unsigned f = 0; | |
136 | #define f_any 1u | |
c81c35df | 137 | |
b1a20bee MW |
138 | /* Build the identification string. */ |
139 | dstr_putf(&d, "%s ", tv->test->name); | |
140 | if (desc) | |
141 | DPUTS(&d, desc); | |
142 | else | |
143 | for (rd = tv->test->regs; rd->name; rd++) | |
144 | if (rd->f&TVRF_ID) { | |
145 | if (f&f_any) DPUTS(&d, ", "); | |
146 | else f |= f_any; | |
147 | dstr_putf(&d, "%s = ", rd->name); | |
148 | rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, TVSF_COMPACT, | |
149 | &dstr_printops, &d); | |
150 | } | |
151 | DPUTS(&d, ": "); | |
c81c35df | 152 | |
b1a20bee MW |
153 | /* Report the benchmark results. */ |
154 | tvec_benchreport(&dstr_printops, &d, unit, 0, t); | |
155 | DPUTZ(&d); | |
c81c35df | 156 | |
b1a20bee MW |
157 | /* Write the result. */ |
158 | tvec_info(tv, "%s", d.buf); | |
159 | DDESTROY(&d); | |
c81c35df | 160 | |
b1a20bee | 161 | #undef f_any |
c81c35df MW |
162 | } |
163 | ||
b1a20bee MW |
164 | const struct tvec_benchoutops tvec_benchoutputfallback = |
165 | { fallback_bbench, fallback_ebench }; | |
c81c35df | 166 | |
b1a20bee | 167 | /*----- Benchmark environment scaffolding ---------------------------------*/ |
c81c35df | 168 | |
b1a20bee | 169 | /* --- @tvec_benchprep@ --- * |
c81c35df | 170 | * |
b1a20bee MW |
171 | * Arguments: @struct tvec_state *tv@ = test vector state |
172 | * @struct bench_state **bst_inout@ = benchmark state (updated) | |
173 | * @unsigned f@ = calibration flags | |
c81c35df | 174 | * |
b1a20bee | 175 | * Returns: Zero on success, %$-1$% on failure. |
c81c35df | 176 | * |
b1a20bee MW |
177 | * Use: If @*bst_inout@ is null then allocate and initialize a fresh |
178 | * benchmark state with a default timer, and @*bst_inout@ is | |
179 | * updated to point to the fresh state. The storage for the | |
180 | * state was allocated using the test vector state's arena. | |
181 | * | |
182 | * If the benchmark state hasn't been calibrated, then this is | |
183 | * done, passing @f@ to @bench_calibrate@. | |
184 | * | |
185 | * On failure, the test group is skipped, reporting a suitable | |
186 | * message, and %$-1$% is returned. If a fresh benchmark state | |
187 | * was allocated, but calibration failed, the state is | |
188 | * %%\emph{not}%% released. | |
c81c35df MW |
189 | */ |
190 | ||
b1a20bee MW |
191 | int tvec_benchprep(struct tvec_state *tv, |
192 | struct bench_state **bst_inout, unsigned f) | |
c81c35df | 193 | { |
b1a20bee MW |
194 | dstr d = DSTR_INIT; |
195 | struct bench_timer *bt; | |
196 | struct bench_state *b; | |
197 | int rc; | |
c81c35df | 198 | |
b1a20bee MW |
199 | /* Set up the benchmarking state if it hasn't been done before. */ |
200 | if (*bst_inout) | |
201 | b = *bst_inout; | |
202 | else { | |
203 | bt = bench_createtimer(0); | |
204 | if (!bt) | |
205 | { tvec_skipgroup(tv, "failed to create timer"); goto timer_failed; } | |
206 | X_NEW(b, tv->a); bench_init(b, bt); *bst_inout = b; | |
5c0f2e08 | 207 | } |
c81c35df | 208 | |
b1a20bee MW |
209 | /* If the timer isn't calibrated yet then do that now. */ |
210 | if (!(b->f&BTF_CLB)) { | |
211 | b->tm->ops->describe(b->tm, &d); | |
212 | tvec_notice(tv, "calibrating timer `%s'...", d.buf); | |
213 | if (bench_calibrate(b, f)) | |
214 | { tvec_skipgroup(tv, "failed to calibrate timer"); goto timer_failed; } | |
215 | } else if (!(b->f&BTF_ANY)) | |
216 | { tvec_skipgroup(tv, "timer broken"); goto timer_failed; } | |
c81c35df | 217 | |
b1a20bee | 218 | rc = 0; goto end; |
5c0f2e08 | 219 | |
b1a20bee MW |
220 | timer_failed: |
221 | /* Give a `helpful' hint if the timer didn't work. */ | |
222 | if (!getenv("MLIB_BENCH_DEBUG")) | |
223 | tvec_notice(tv, "set `MLIB_BENCH_DEBUG=t' in the environment " | |
224 | "for more detail"); | |
225 | rc = -1; | |
226 | goto end; | |
c81c35df | 227 | |
b1a20bee MW |
228 | end: |
229 | dstr_destroy(&d); | |
230 | return (rc); | |
231 | } | |
67b5031e MW |
232 | |
233 | /* --- @tvec_benchsetup@ --- * | |
234 | * | |
235 | * Arguments: @struct tvec_state *tv@ = test vector state | |
236 | * @const struct tvec_env *env@ = environment description | |
237 | * @void *pctx@ = parent context (ignored) | |
238 | * @void *ctx@ = context pointer to initialize | |
239 | * | |
c91413e6 | 240 | * Returns: --- |
67b5031e MW |
241 | * |
242 | * Use: Initialize a benchmarking environment context. | |
243 | * | |
244 | * The environment description must really be a @struct | |
c91413e6 | 245 | * tvec_benchenv@. If the @bst@ slot is null, then a temporary |
67b5031e MW |
246 | * benchmark state is allocated for the current test group and |
247 | * released at the end. Otherwise, it must be the address of a | |
248 | * pointer to a benchmark state: if the pointer is null, then a | |
249 | * fresh state is allocated and initialized and the pointer is | |
250 | * updated; otherwise, the pointer is assumed to refer to an | |
251 | * existing valid benchmark state. | |
252 | */ | |
253 | ||
c91413e6 MW |
254 | void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, |
255 | void *pctx, void *ctx) | |
e63124bc MW |
256 | { |
257 | struct tvec_benchctx *bc = ctx; | |
c91413e6 MW |
258 | const struct tvec_benchenv *be = (const struct tvec_benchenv *)env; |
259 | const struct tvec_env *subenv = be->env; | |
e63124bc | 260 | |
c91413e6 | 261 | bc->be = be; bc->bst = 0; bc->subctx = 0; |
b1a20bee MW |
262 | if (!tvec_benchprep(tv, be->bst, BTF_INDIRECT)) |
263 | { bc->bst = *be->bst; bc->dflt_target = bc->bst->target_s; } | |
264 | if (subenv && subenv->ctxsz) bc->subctx = x_alloc(tv->a, subenv->ctxsz); | |
c91413e6 | 265 | if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx); |
e63124bc MW |
266 | } |
267 | ||
814e42ff | 268 | /* --- @tvec_benchfindvar@, @setvar@ --- * |
67b5031e MW |
269 | * |
270 | * Arguments: @struct tvec_state *tv@ = test vector state | |
271 | * @const char *var@ = variable name to set | |
814e42ff MW |
272 | * @const union tvec_regval *rv@ = register value |
273 | * @void **ctx_out@ = where to put the @setvar@ context | |
67b5031e MW |
274 | * @void *ctx@ = context pointer |
275 | * | |
814e42ff MW |
276 | * Returns: @tvec_benchfindvar@ returns a pointer to the variable |
277 | * definition, or null; @setvar@ returns zero on success or | |
278 | * %$-1$% on error. | |
67b5031e | 279 | * |
814e42ff MW |
280 | * Use: Find a definition for a special variable. The following |
281 | * special variables are supported. | |
67b5031e | 282 | * |
814e42ff | 283 | * * %|@target|% is the (approximate) duration to run |
67b5031e MW |
284 | * the benchmark. |
285 | * | |
286 | * Unrecognized variables are passed to the subordinate | |
287 | * environment, if there is one. | |
288 | */ | |
289 | ||
814e42ff MW |
290 | static int setvar(struct tvec_state *tv, const char *var, |
291 | const union tvec_regval *rv, void *ctx) | |
e63124bc MW |
292 | { |
293 | struct tvec_benchctx *bc = ctx; | |
e63124bc MW |
294 | |
295 | if (STRCMP(var, ==, "@target")) { | |
6e683a79 | 296 | if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var)); |
814e42ff MW |
297 | bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG; |
298 | } else assert("unknown var"); | |
299 | return (0); | |
300 | } | |
301 | ||
302 | static const struct tvec_vardef target_var = | |
d056fbdf | 303 | { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } }; |
814e42ff MW |
304 | |
305 | const struct tvec_vardef *tvec_benchfindvar | |
306 | (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx) | |
307 | { | |
308 | struct tvec_benchctx *bc = ctx; | |
309 | const struct tvec_benchenv *be = bc->be; | |
310 | const struct tvec_env *subenv = be->env; | |
311 | ||
312 | if (STRCMP(var, ==, "@target")) { *ctx_out = bc; return (&target_var); } | |
313 | else if (subenv && subenv->findvar) | |
314 | return (subenv->findvar(tv, var, ctx_out, bc->subctx)); | |
315 | else return (0); | |
e63124bc MW |
316 | } |
317 | ||
67b5031e MW |
318 | /* --- @tvec_benchbefore@ --- * |
319 | * | |
320 | * Arguments: @struct tvec_state *tv@ = test vector state | |
321 | * @void *ctx@ = context pointer | |
322 | * | |
c91413e6 | 323 | * Returns: --- |
67b5031e MW |
324 | * |
325 | * Use: Invoke the subordinate environment's @before@ function to | |
326 | * prepare for the benchmark. | |
327 | */ | |
328 | ||
c91413e6 | 329 | void tvec_benchbefore(struct tvec_state *tv, void *ctx) |
e63124bc MW |
330 | { |
331 | struct tvec_benchctx *bc = ctx; | |
c91413e6 MW |
332 | const struct tvec_benchenv *be = bc->be; |
333 | const struct tvec_env *subenv = be->env; | |
e63124bc | 334 | |
c91413e6 | 335 | if (subenv && subenv->before) subenv->before(tv, bc->subctx); |
e63124bc MW |
336 | } |
337 | ||
67b5031e MW |
338 | /* --- @tvec_benchrun@ --- * |
339 | * | |
340 | * Arguments: @struct tvec_state *tv@ = test vector state | |
341 | * @tvec_testfn *fn@ = test function to run | |
342 | * @void *ctx@ = context pointer for the test function | |
343 | * | |
344 | * Returns: --- | |
345 | * | |
346 | * Use: Measures and reports the performance of a test function. | |
67b5031e | 347 | */ |
e63124bc MW |
348 | |
349 | void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) | |
350 | { | |
351 | struct tvec_benchctx *bc = ctx; | |
c91413e6 MW |
352 | const struct tvec_benchenv *be = bc->be; |
353 | const struct tvec_env *subenv = be->env; | |
e63124bc | 354 | struct tvec_output *o = tv->output; |
b1a20bee MW |
355 | struct tvec_fallbackoutput fo; |
356 | const struct tvec_benchoutops *bo; | |
357 | BENCH_MEASURE_DECLS; | |
358 | struct bench_timing t; | |
359 | unsigned long *n; | |
e63124bc | 360 | unsigned unit; |
e63124bc | 361 | double base; |
b1a20bee | 362 | int rc; |
e63124bc | 363 | |
67b5031e | 364 | /* Decide on the kind of unit and the base count. */ |
c91413e6 | 365 | base = be->niter; |
b1a20bee MW |
366 | if (be->rbuf < 0) unit = BTU_OP; |
367 | else { unit = BTU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; } | |
e63124bc | 368 | |
b1a20bee MW |
369 | /* Find the output operations and report the start. */ |
370 | bo = tvec_outputext(tv, &o, &fo, | |
371 | TVEC_BENCHOUTEXT, &tvec_benchoutputfallback); | |
372 | bo->bbench(o, 0, unit); | |
e63124bc | 373 | |
b1a20bee MW |
374 | /* Run the benchmark. */ |
375 | if (be->riter >= 0) { | |
376 | n = &TVEC_REG(tv, in, be->riter)->v.u; | |
377 | if (subenv && subenv->run) | |
378 | BENCH_MEASURE(bc->bst, rc, &t, base) | |
379 | { *n = _bench_n; subenv->run(tv, fn, bc->subctx); } | |
380 | else | |
381 | BENCH_MEASURE(bc->bst, rc, &t, base) | |
382 | { *n = _bench_n; fn(tv->in, tv->out, bc->subctx); } | |
383 | } else { | |
384 | if (subenv && subenv->run) | |
385 | BENCH_MEASURE(bc->bst, rc, &t, base) | |
386 | while (_bench_n--) subenv->run(tv, fn, bc->subctx); | |
387 | else | |
388 | BENCH_MEASURE(bc->bst, rc, &t, base) | |
389 | while (_bench_n--) fn(tv->in, tv->out, bc->subctx); | |
390 | } | |
e63124bc | 391 | |
b1a20bee MW |
392 | /* Report the outcome. */ |
393 | bo->ebench(o, 0, unit, rc ? 0 : &t); | |
e63124bc MW |
394 | } |
395 | ||
c81c35df | 396 | /* --- @tvec_benchafter@ --- * |
67b5031e | 397 | * |
c81c35df MW |
398 | * Arguments: @struct tvec_state *tv@ = test vector state |
399 | * @void *ctx@ = context pointer | |
67b5031e MW |
400 | * |
401 | * Returns: --- | |
402 | * | |
c81c35df MW |
403 | * Use: Invoke the subordinate environment's @after@ function to |
404 | * clean up after the benchmark. | |
67b5031e MW |
405 | */ |
406 | ||
c81c35df | 407 | void tvec_benchafter(struct tvec_state *tv, void *ctx) |
e63124bc | 408 | { |
c81c35df MW |
409 | struct tvec_benchctx *bc = ctx; |
410 | const struct tvec_benchenv *be = bc->be; | |
411 | const struct tvec_env *subenv = be->env; | |
e63124bc | 412 | |
c81c35df | 413 | /* Restore the benchmark state's old target. */ |
13ee7406 | 414 | if (bc->bst) bc->bst->target_s = bc->dflt_target; |
c81c35df | 415 | bc->f &= ~TVBF_SETTRG; |
e63124bc | 416 | |
c81c35df MW |
417 | /* Pass the call on to the subsidiary environment. */ |
418 | if (subenv && subenv->after) subenv->after(tv, bc->subctx); | |
419 | } | |
e63124bc | 420 | |
c81c35df MW |
421 | /* --- @tvec_benchteardown@ --- * |
422 | * | |
423 | * Arguments: @struct tvec_state *tv@ = test vector state | |
424 | * @void *ctx@ = context pointer | |
425 | * | |
426 | * Returns: --- | |
427 | * | |
428 | * Use: Tear down the benchmark environment. | |
429 | */ | |
e63124bc | 430 | |
c81c35df MW |
431 | void tvec_benchteardown(struct tvec_state *tv, void *ctx) |
432 | { | |
433 | struct tvec_benchctx *bc = ctx; | |
434 | const struct tvec_benchenv *be; | |
435 | const struct tvec_env *subenv; | |
e63124bc | 436 | |
c81c35df MW |
437 | be = bc->be; subenv = be->env; |
438 | ||
439 | /* Tear down any subsidiary environment. */ | |
b1a20bee MW |
440 | if (subenv && subenv->teardown) subenv->teardown(tv, bc->subctx); |
441 | if (bc->subctx) x_free(tv->a, bc->subctx); | |
c81c35df MW |
442 | |
443 | /* If the benchmark state was temporary, then dispose of it. */ | |
444 | if (bc->bst) { | |
445 | if (be->bst) bc->bst->target_s = bc->dflt_target; | |
b1a20bee | 446 | else { bench_destroy(bc->bst); x_free(tv->a, bc->bst); } |
e63124bc MW |
447 | } |
448 | } | |
449 | ||
450 | /*----- That's all, folks -------------------------------------------------*/ |