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 /*----- Data structures ---------------------------------------------------*/
36 struct tvec_state *tv; /* test vector state */
37 const struct tvec_env *env; /* subordinate environment */
38 void *ctx; /* subordinate env's context */
39 unsigned long *n; /* iteration count address */
40 const struct tvec_reg *in; struct tvec_reg *out; /* register vectors */
41 tvec_testfn *fn; /* test function to run */
44 /*----- Global variables --------------------------------------------------*/
46 struct bench_state *tvec_benchstate; /* common benchmarking state */
48 /*----- Utilities ---------------------------------------------------------*/
50 /* --- @normalize@ --- *
52 * Arguments: @double *x_inout@ = address of a value to normalize
53 * @const char **unit_out@ = address to store unit prefix
54 * @double scale@ = scale factor for unit steps
58 * Use: Adjust @*x_inout@ by a power of @scale@, and set @*unit_out@
59 * so that printing the two reflects the original value with an
60 * appropriate SI unit scaling. The @scale@ should be 1024 for
61 * binary quantities, most notably memory sizes, or 1000 for
65 static void normalize(double *x_inout, const char **unit_out, double scale)
69 *const big[] = { "k", "M", "G", "T", "P", "E", 0 },
70 *const little[] = { "m", "ยต", "n", "p", "f", "a", 0 };
75 for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale);
77 for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale);
81 *x_inout = x; *unit_out = *u;
84 /* --- @benchloop_...@ --- *
86 * Arguments: @unsigned long n@ = iteration count
87 * @void *ctx@ = benchmark running context
91 * Use: Run various kinds of benchmarking loops.
93 * * The @..._outer_...@ functions call the underlying
94 * function @n@ times in a loop; by contrast, the
95 * @..._inner_...@ functions set a register value to the
96 * chosen iteration count and expect the underlying function
97 * to perform the loop itself.
99 * * The @..._direct@ functions just call the underlying test
100 * function directly (though still through an `indirect
101 * jump' instruction); by contrast, the @..._indirect@
102 * functions invoke a subsidiary environment's @run@
103 * function, which adds additional overhead.
106 static void benchloop_outer_direct(unsigned long n, void *ctx)
108 struct benchrun *r = ctx;
109 tvec_testfn *fn = r->fn; void *tctx = r->ctx;
110 const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out;
112 while (n--) fn(in, out, tctx);
115 static void benchloop_inner_direct(unsigned long n, void *ctx)
116 { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); }
118 static void benchloop_outer_indirect(unsigned long n, void *ctx)
120 struct benchrun *r = ctx;
121 struct tvec_state *tv = r->tv;
122 void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run;
123 tvec_testfn *fn = r->fn; void *tctx = r->ctx;
125 while (n--) run(tv, fn, tctx);
128 static void benchloop_inner_indirect(unsigned long n, void *ctx)
129 { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
131 /*----- Output utilities --------------------------------------------------*/
133 /* --- @tvec_benchreport@ --- *
135 * Arguments: @const struct gprintf_ops *gops@ = print operations
136 * @void *go@ = print destination
137 * @unsigned unit@ = the unit being measured (~TVBU_...@)
138 * @const struct bench_timing *tm@ = the benchmark result
142 * Use: Formats a report about the benchmark performance. This
143 * function is intended to be called on by an output
147 void tvec_benchreport(const struct gprintf_ops *gops, void *go,
148 unsigned unit, const struct bench_timing *tm)
150 double scale, x, n = tm->n;
151 const char *u, *what, *whats;
153 if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; }
155 assert(tm->f&BTF_TIMEOK);
159 gprintf(gops, go, "%.0f iterations ", n);
160 what = "op"; whats = "ops"; scale = 1000;
163 x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u);
164 what = whats = "B"; scale = 1024;
170 x = tm->t; normalize(&x, &u, 1000);
171 gprintf(gops, go, "in %.3f %ss", x, u);
172 if (tm->f&BTF_CYOK) {
173 x = tm->cy; normalize(&x, &u, 1000);
174 gprintf(gops, go, " (%.3f %scy)", x, u);
176 gprintf(gops, go, ": ");
178 x = n/tm->t; normalize(&x, &u, scale);
179 gprintf(gops, go, "%.3f %s%s/s", x, u, whats);
180 x = tm->t/n; normalize(&x, &u, 1000);
181 gprintf(gops, go, ", %.3f %ss/%s", x, u, what);
182 if (tm->f&BTF_CYOK) {
183 x = tm->cy/n; normalize(&x, &u, 1000);
184 gprintf(gops, go, " (%.3f %scy/%s)", x, u, what);
188 /*----- Benchmark environment scaffolding ---------------------------------*/
190 /* --- @tvec_benchsetup@ --- *
192 * Arguments: @struct tvec_state *tv@ = test vector state
193 * @const struct tvec_env *env@ = environment description
194 * @void *pctx@ = parent context (ignored)
195 * @void *ctx@ = context pointer to initialize
199 * Use: Initialize a benchmarking environment context.
201 * The environment description must really be a @struct
202 * tvec_benchenv@. If the @bst@ slot is null, then a temporary
203 * benchmark state is allocated for the current test group and
204 * released at the end. Otherwise, it must be the address of a
205 * pointer to a benchmark state: if the pointer is null, then a
206 * fresh state is allocated and initialized and the pointer is
207 * updated; otherwise, the pointer is assumed to refer to an
208 * existing valid benchmark state.
211 void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
212 void *pctx, void *ctx)
214 struct tvec_benchctx *bc = ctx;
215 const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
216 const struct tvec_env *subenv = be->env;
217 struct bench_timer *bt;
219 /* Basic initialization. */
220 bc->be = be; bc->bst = 0; bc->subctx = 0;
222 /* Set up the benchmarking state if it hasn't been done before. */
223 if (!be->bst || !*be->bst) {
224 bt = bench_createtimer(); if (!bt) goto fail_timer;
225 bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt);
226 if (be->bst) *be->bst = bc->bst;
227 } else if (!(*be->bst)->tm)
232 /* Set the default target time. */
233 bc->dflt_target = bc->bst->target_s;
235 /* Initialize the subordinate environment. */
236 if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
237 if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
243 tvec_skipgroup(tv, "failed to create timer"); goto end;
246 /* --- @tvec_benchfindvar@, @setvar@ --- *
248 * Arguments: @struct tvec_state *tv@ = test vector state
249 * @const char *var@ = variable name to set
250 * @const union tvec_regval *rv@ = register value
251 * @void **ctx_out@ = where to put the @setvar@ context
252 * @void *ctx@ = context pointer
254 * Returns: @tvec_benchfindvar@ returns a pointer to the variable
255 * definition, or null; @setvar@ returns zero on success or
258 * Use: Find a definition for a special variable. The following
259 * special variables are supported.
261 * * %|@target|% is the (approximate) duration to run
264 * Unrecognized variables are passed to the subordinate
265 * environment, if there is one.
268 static int setvar(struct tvec_state *tv, const char *var,
269 const union tvec_regval *rv, void *ctx)
271 struct tvec_benchctx *bc = ctx;
273 if (STRCMP(var, ==, "@target")) {
274 if (bc->f&TVBF_SETTRG) return (tvec_dupreg(tv, var));
275 bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG;
276 } else assert("unknown var");
280 static const struct tvec_vardef target_var =
281 { sizeof(struct tvec_reg), setvar, { "@target", -1, &tvty_duration, 0 } };
283 const struct tvec_vardef *tvec_benchfindvar
284 (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
286 struct tvec_benchctx *bc = ctx;
287 const struct tvec_benchenv *be = bc->be;
288 const struct tvec_env *subenv = be->env;
290 if (STRCMP(var, ==, "@target")) { *ctx_out = bc; return (&target_var); }
291 else if (subenv && subenv->findvar)
292 return (subenv->findvar(tv, var, ctx_out, bc->subctx));
296 /* --- @tvec_benchbefore@ --- *
298 * Arguments: @struct tvec_state *tv@ = test vector state
299 * @void *ctx@ = context pointer
303 * Use: Invoke the subordinate environment's @before@ function to
304 * prepare for the benchmark.
307 void tvec_benchbefore(struct tvec_state *tv, void *ctx)
309 struct tvec_benchctx *bc = ctx;
310 const struct tvec_benchenv *be = bc->be;
311 const struct tvec_env *subenv = be->env;
313 if (subenv && subenv->before) subenv->before(tv, bc->subctx);
316 /* --- @tvec_benchrun@ --- *
318 * Arguments: @struct tvec_state *tv@ = test vector state
319 * @tvec_testfn *fn@ = test function to run
320 * @void *ctx@ = context pointer for the test function
324 * Use: Measures and reports the performance of a test function.
327 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
329 struct tvec_benchctx *bc = ctx;
330 const struct tvec_benchenv *be = bc->be;
331 const struct tvec_env *subenv = be->env;
332 const struct tvec_regdef *rd;
333 struct tvec_output *o = tv->output;
334 struct bench_timing tm;
343 /* Fill in the easy parts of the run context. */
344 r.tv = tv; r.env = subenv; r.ctx = bc->subctx;
345 r.in = tv->in; r.out = tv->out; r.fn = fn;
347 /* Decide on the run function to select. */
348 if (be->riter >= 0) {
349 r.n = &TVEC_REG(tv, in, be->riter)->v.u;
350 loopfn = subenv && subenv->run ?
351 benchloop_inner_indirect : benchloop_inner_direct;
354 loopfn = subenv && subenv->run ?
355 benchloop_outer_indirect : benchloop_outer_direct;
358 /* Decide on the kind of unit and the base count. */
360 if (be->rbuf < 0) unit = TVBU_OP;
361 else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
363 /* Construct a description of the test using the identifier registers. */
364 for (rd = tv->test->regs; rd->name; rd++)
366 if (f&f_any) dstr_puts(&d, ", ");
368 dstr_putf(&d, "%s = ", rd->name);
369 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd,
370 TVSF_COMPACT, &dstr_printops, &d);
373 /* Run the benchmark. */
374 o->ops->bbench(o, d.buf, unit);
375 if (bench_measure(&tm, bc->bst, base, loopfn, &r))
376 o->ops->ebench(o, d.buf, unit, 0);
378 o->ops->ebench(o, d.buf, unit, &tm);
385 /* --- @tvec_benchafter@ --- *
387 * Arguments: @struct tvec_state *tv@ = test vector state
388 * @void *ctx@ = context pointer
392 * Use: Invoke the subordinate environment's @after@ function to
393 * clean up after the benchmark.
396 void tvec_benchafter(struct tvec_state *tv, void *ctx)
398 struct tvec_benchctx *bc = ctx;
399 const struct tvec_benchenv *be = bc->be;
400 const struct tvec_env *subenv = be->env;
402 /* Restore the benchmark state's old target. */
403 bc->bst->target_s = bc->dflt_target;
404 bc->f &= ~TVBF_SETTRG;
406 /* Pass the call on to the subsidiary environment. */
407 if (subenv && subenv->after) subenv->after(tv, bc->subctx);
410 /* --- @tvec_benchteardown@ --- *
412 * Arguments: @struct tvec_state *tv@ = test vector state
413 * @void *ctx@ = context pointer
417 * Use: Tear down the benchmark environment.
420 void tvec_benchteardown(struct tvec_state *tv, void *ctx)
422 struct tvec_benchctx *bc = ctx;
423 const struct tvec_benchenv *be;
424 const struct tvec_env *subenv;
426 be = bc->be; subenv = be->env;
428 /* Tear down any subsidiary environment. */
429 if (subenv && subenv->teardown)
430 subenv->teardown(tv, bc->subctx);
432 /* If the benchmark state was temporary, then dispose of it. */
434 if (be->bst) bc->bst->target_s = bc->dflt_target;
435 else { bench_destroy(bc->bst); xfree(bc->bst); }
439 /*----- That's all, folks -------------------------------------------------*/