@@@ fltfmt mess
[mLib] / test / tvec-bench.c
CommitLineData
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
39struct 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 50struct 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
69void 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
106static 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
127static 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
164const 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
191int 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
220timer_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
228end:
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
254void 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
290static 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
302static const struct tvec_vardef target_var =
d056fbdf 303 { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } };
814e42ff
MW
304
305const 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 329void 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
349void 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 407void 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
431void 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 -------------------------------------------------*/