3 * Benchmarking in the test-vector framework
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
33 #include "tvec-bench.h"
34 #include "tvec-output.h"
35 #include "tvec-types.h"
37 /*----- Data structures ---------------------------------------------------*/
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 */
48 /*----- Global variables --------------------------------------------------*/
50 struct bench_state *tvec_benchstate; /* common benchmarking state */
52 /*----- Output utilities --------------------------------------------------*/
54 /* --- @tvec_benchreport@ --- *
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
64 * Use: Formats a report about the benchmark performance. This
65 * function is intended to be called on by an output
69 void tvec_benchreport(const struct gprintf_ops *gops, void *go,
70 unsigned unit, unsigned style,
71 const struct bench_timing *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);
80 case BTU_OP: gprintf(gops, go, "ops=%.0f", t->n); break;
81 case BTU_BYTE: gprintf(gops, go, "bytes=%.0f", t->n); break;
84 gprintf(gops, go, " sec=%.6g", t->t);
85 if (t->f&BTF_CYOK) gprintf(gops, go, " cy=%.0f", t->cy);
89 /*----- Default output implementation -------------------------------------*/
91 /* --- @fallback_bbench@ --- *
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_...@)
100 * Use: Report that a benchmark has started.
102 * The fallback implementation does nothing here. All of the
103 * reporting happens in @fallback_ebench@.
106 static void fallback_bbench(struct tvec_output *o,
107 const char *desc, unsigned unit)
110 /* --- @fallback_ebench@ --- *
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
120 * Use: Report a benchmark's results
122 * The fallback implementation just delegates to the default
123 * benchmark reporting to produce a line written through the
124 * standard @report@ output operation.
127 static void fallback_ebench(struct tvec_output *o,
128 const char *desc, unsigned unit,
129 const struct bench_timing *t)
131 struct tvec_fallbackoutput *fo = (struct tvec_fallbackoutput *)o;
132 struct tvec_state *tv = fo->tv;
133 const struct tvec_regdef *rd;
138 /* Build the identification string. */
139 dstr_putf(&d, "%s ", tv->test->name);
143 for (rd = tv->test->regs; rd->name; rd++)
145 if (f&f_any) DPUTS(&d, ", ");
147 dstr_putf(&d, "%s = ", rd->name);
148 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, TVSF_COMPACT,
153 /* Report the benchmark results. */
154 tvec_benchreport(&dstr_printops, &d, unit, 0, t);
157 /* Write the result. */
158 tvec_info(tv, "%s", d.buf);
164 const struct tvec_benchoutops tvec_benchoutputfallback =
165 { fallback_bbench, fallback_ebench };
167 /*----- Benchmark environment scaffolding ---------------------------------*/
169 /* --- @tvec_benchprep@ --- *
171 * Arguments: @struct tvec_state *tv@ = test vector state
172 * @struct bench_state **bst_inout@ = benchmark state (updated)
173 * @unsigned f@ = calibration flags
175 * Returns: Zero on success, %$-1$% on failure.
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.
182 * If the benchmark state hasn't been calibrated, then this is
183 * done, passing @f@ to @bench_calibrate@.
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.
191 int tvec_benchprep(struct tvec_state *tv,
192 struct bench_state **bst_inout, unsigned f)
195 struct bench_timer *bt;
196 struct bench_state *b;
199 /* Set up the benchmarking state if it hasn't been done before. */
203 bt = bench_createtimer(0);
205 { tvec_skipgroup(tv, "failed to create timer"); goto timer_failed; }
206 X_NEW(b, tv->a); bench_init(b, bt); *bst_inout = b;
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; }
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 "
233 /* --- @tvec_benchsetup@ --- *
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
242 * Use: Initialize a benchmarking environment context.
244 * The environment description must really be a @struct
245 * tvec_benchenv@. If the @bst@ slot is null, then a temporary
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.
254 void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
255 void *pctx, void *ctx)
257 struct tvec_benchctx *bc = ctx;
258 const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
259 const struct tvec_env *subenv = be->env;
261 bc->be = be; bc->bst = 0; bc->subctx = 0;
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)
265 bc->subctx = pool_alloc(tv->p_group, subenv->ctxsz);
266 if (subenv && subenv->setup)
267 subenv->setup(tv, subenv, bc, bc->subctx);
270 /* --- @tvec_benchfindvar@, @setvar@ --- *
272 * Arguments: @struct tvec_state *tv@ = test vector state
273 * @const char *var@ = variable name to set
274 * @const union tvec_regval *rv@ = register value
275 * @void **ctx_out@ = where to put the @setvar@ context
276 * @void *ctx@ = context pointer
278 * Returns: @tvec_benchfindvar@ returns a pointer to the variable
279 * definition, or null; @setvar@ returns zero on success or
282 * Use: Find a definition for a special variable. The following
283 * special variables are supported.
285 * * %|@target|% is the (approximate) duration to run
288 * Unrecognized variables are passed to the subordinate
289 * environment, if there is one.
292 static int setvar(struct tvec_state *tv, const char *var,
293 const union tvec_regval *rv, void *ctx)
295 struct tvec_benchctx *bc = ctx;
297 if (STRCMP(var, ==, "@target")) {
298 if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var));
299 bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG;
300 } else assert("unknown var");
304 static const struct tvec_vardef target_var =
305 { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } };
307 const struct tvec_vardef *tvec_benchfindvar
308 (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
310 struct tvec_benchctx *bc = ctx;
311 const struct tvec_benchenv *be = bc->be;
312 const struct tvec_env *subenv = be->env;
314 if (STRCMP(var, ==, "@target")) { *ctx_out = bc; return (&target_var); }
315 else if (subenv && subenv->findvar)
316 return (subenv->findvar(tv, var, ctx_out, bc->subctx));
320 /* --- @tvec_benchbefore@ --- *
322 * Arguments: @struct tvec_state *tv@ = test vector state
323 * @void *ctx@ = context pointer
327 * Use: Invoke the subordinate environment's @before@ function to
328 * prepare for the benchmark.
331 void tvec_benchbefore(struct tvec_state *tv, void *ctx)
333 struct tvec_benchctx *bc = ctx;
334 const struct tvec_benchenv *be = bc->be;
335 const struct tvec_env *subenv = be->env;
337 if (subenv && subenv->before) subenv->before(tv, bc->subctx);
340 /* --- @tvec_benchrun@ --- *
342 * Arguments: @struct tvec_state *tv@ = test vector state
343 * @tvec_testfn *fn@ = test function to run
344 * @void *ctx@ = context pointer for the test function
348 * Use: Measures and reports the performance of a test function.
351 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
353 struct tvec_benchctx *bc = ctx;
354 const struct tvec_benchenv *be = bc->be;
355 const struct tvec_env *subenv = be->env;
356 struct tvec_output *o = tv->output;
357 struct tvec_fallbackoutput fo;
358 const struct tvec_benchoutops *bo;
360 struct bench_timing t;
366 /* Decide on the kind of unit and the base count. */
368 if (be->rbuf < 0) unit = BTU_OP;
369 else { unit = BTU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
371 /* Find the output operations and report the start. */
372 bo = tvec_outputext(tv, &o, &fo,
373 TVEC_BENCHOUTEXT, &tvec_benchoutputfallback);
374 bo->bbench(o, 0, unit);
376 /* Run the benchmark. */
377 if (be->riter >= 0) {
378 n = &TVEC_REG(tv, in, be->riter)->v.u;
379 if (subenv && subenv->run)
380 BENCH_MEASURE(bc->bst, rc, &t, base)
381 { *n = _bench_n; subenv->run(tv, fn, bc->subctx); }
383 BENCH_MEASURE(bc->bst, rc, &t, base)
384 { *n = _bench_n; fn(tv->in, tv->out, bc->subctx); }
386 if (subenv && subenv->run)
387 BENCH_MEASURE(bc->bst, rc, &t, base)
388 while (_bench_n--) subenv->run(tv, fn, bc->subctx);
390 BENCH_MEASURE(bc->bst, rc, &t, base)
391 while (_bench_n--) fn(tv->in, tv->out, bc->subctx);
394 /* Report the outcome. */
395 bo->ebench(o, 0, unit, rc ? 0 : &t);
398 /* --- @tvec_benchafter@ --- *
400 * Arguments: @struct tvec_state *tv@ = test vector state
401 * @void *ctx@ = context pointer
405 * Use: Invoke the subordinate environment's @after@ function to
406 * clean up after the benchmark.
409 void tvec_benchafter(struct tvec_state *tv, void *ctx)
411 struct tvec_benchctx *bc = ctx;
412 const struct tvec_benchenv *be = bc->be;
413 const struct tvec_env *subenv = be->env;
415 /* Restore the benchmark state's old target. */
416 if (bc->bst) bc->bst->target_s = bc->dflt_target;
417 bc->f &= ~TVBF_SETTRG;
419 /* Pass the call on to the subsidiary environment. */
420 if (subenv && subenv->after) subenv->after(tv, bc->subctx);
423 /* --- @tvec_benchteardown@ --- *
425 * Arguments: @struct tvec_state *tv@ = test vector state
426 * @void *ctx@ = context pointer
430 * Use: Tear down the benchmark environment.
433 void tvec_benchteardown(struct tvec_state *tv, void *ctx)
435 struct tvec_benchctx *bc = ctx;
436 const struct tvec_benchenv *be;
437 const struct tvec_env *subenv;
439 be = bc->be; subenv = be->env;
441 /* Tear down any subsidiary environment. */
442 if (subenv && subenv->teardown) subenv->teardown(tv, bc->subctx);
444 /* If the benchmark state was temporary, then dispose of it. */
446 if (be->bst) bc->bst->target_s = bc->dflt_target;
447 else { bench_destroy(bc->bst); x_free(tv->a, bc->bst); }
451 /*----- That's all, folks -------------------------------------------------*/