@@@ misc wip
[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"
31#include "tvec.h"
32
33/*----- Data structures ---------------------------------------------------*/
34
35struct benchrun {
67b5031e
MW
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 */
e63124bc
MW
42};
43
44/*----- Global variables --------------------------------------------------*/
45
67b5031e 46struct bench_state *tvec_benchstate; /* common benchmarking state */
e63124bc 47
67b5031e
MW
48/*----- Utilities ---------------------------------------------------------*/
49
50/* --- @normalize@ --- *
51 *
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
55 *
56 * Returns: ---
57 *
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
62 * other quantities.
63 */
e63124bc
MW
64
65static void normalize(double *x_inout, const char **unit_out, double scale)
66{
67 static const char
68 *const nothing = "",
69 *const big[] = { "k", "M", "G", "T", "P", "E", 0 },
70 *const little[] = { "m", "ยต", "n", "p", "f", "a", 0 };
71 const char *const *u;
72 double x = *x_inout;
73
74 if (x < 1)
75 for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale);
76 else if (x >= scale)
77 for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale);
78 else
79 u = &nothing;
80
81 *x_inout = x; *unit_out = *u;
82}
83
c81c35df
MW
84/* --- @benchloop_...@ --- *
85 *
86 * Arguments: @unsigned long n@ = iteration count
87 * @void *ctx@ = benchmark running context
88 *
89 * Returns: ---
90 *
91 * Use: Run various kinds of benchmarking loops.
92 *
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.
98 *
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.
104 */
105
106static void benchloop_outer_direct(unsigned long n, void *ctx)
107{
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;
111
112 while (n--) fn(in, out, tctx);
113}
114
115static 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); }
117
118static void benchloop_outer_indirect(unsigned long n, void *ctx)
119{
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;
124
125 while (n--) run(tv, fn, tctx);
126}
127
128static 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); }
130
131/*----- Output utilities --------------------------------------------------*/
132
133/* --- @tvec_benchreport@ --- *
134 *
135 * Arguments: @const struct gprintf_ops *gops@ = print operations
136 * @void *go@ = print destination
137 * @unsigned unit@ = the unit being measured (~TVBU_...@)
5c0f2e08 138 * @unsigned style@ = output style (@TVSF_...@)
c81c35df
MW
139 * @const struct bench_timing *tm@ = the benchmark result
140 *
141 * Returns: ---
142 *
143 * Use: Formats a report about the benchmark performance. This
144 * function is intended to be called on by an output
145 * @ebench@ function.
146 */
147
148void tvec_benchreport(const struct gprintf_ops *gops, void *go,
5c0f2e08
MW
149 unsigned unit, unsigned style,
150 const struct bench_timing *tm)
c81c35df
MW
151{
152 double scale, x, n = tm->n;
153 const char *u, *what, *whats;
154
5c0f2e08
MW
155 if (!tm) {
156 if (style&TVSF_RAW) gprintf(gops, go, "FAILED");
157 else gprintf(gops, go, "benchmark FAILED");
158 return;
159 }
c81c35df
MW
160
161 assert(tm->f&BTF_TIMEOK);
162
163 switch (unit) {
164 case TVBU_OP:
5c0f2e08
MW
165 if (style&TVSF_RAW) gprintf(gops, go, "ops=%.0f", n);
166 else gprintf(gops, go, "%.0f iterations ", n);
c81c35df
MW
167 what = "op"; whats = "ops"; scale = 1000;
168 break;
169 case TVBU_BYTE:
5c0f2e08
MW
170 if (style&TVSF_RAW)
171 gprintf(gops, go, "bytes=%.0f", n);
172 else {
173 x = n;
174 normalize(&x, &u, 1024); gprintf(gops, go, " %.3f %sB ", x, u);
175 }
c81c35df
MW
176 what = whats = "B"; scale = 1024;
177 break;
178 default:
179 abort();
180 }
181
5c0f2e08
MW
182 if (style&TVSF_RAW) {
183 gprintf(gops, go, " sec=%.6g", tm->t);
184 if (tm->f&BTF_CYOK) gprintf(gops, go, " cy=%.0f", tm->cy);
185 return;
186 }
187
c81c35df
MW
188 x = tm->t; normalize(&x, &u, 1000);
189 gprintf(gops, go, "in %.3f %ss", x, u);
190 if (tm->f&BTF_CYOK) {
191 x = tm->cy; normalize(&x, &u, 1000);
192 gprintf(gops, go, " (%.3f %scy)", x, u);
193 }
194 gprintf(gops, go, ": ");
195
196 x = n/tm->t; normalize(&x, &u, scale);
197 gprintf(gops, go, "%.3f %s%s/s", x, u, whats);
198 x = tm->t/n; normalize(&x, &u, 1000);
199 gprintf(gops, go, ", %.3f %ss/%s", x, u, what);
200 if (tm->f&BTF_CYOK) {
201 x = tm->cy/n; normalize(&x, &u, 1000);
202 gprintf(gops, go, " (%.3f %scy/%s)", x, u, what);
203 }
204}
205
67b5031e
MW
206/*----- Benchmark environment scaffolding ---------------------------------*/
207
208/* --- @tvec_benchsetup@ --- *
209 *
210 * Arguments: @struct tvec_state *tv@ = test vector state
211 * @const struct tvec_env *env@ = environment description
212 * @void *pctx@ = parent context (ignored)
213 * @void *ctx@ = context pointer to initialize
214 *
c91413e6 215 * Returns: ---
67b5031e
MW
216 *
217 * Use: Initialize a benchmarking environment context.
218 *
219 * The environment description must really be a @struct
c91413e6 220 * tvec_benchenv@. If the @bst@ slot is null, then a temporary
67b5031e
MW
221 * benchmark state is allocated for the current test group and
222 * released at the end. Otherwise, it must be the address of a
223 * pointer to a benchmark state: if the pointer is null, then a
224 * fresh state is allocated and initialized and the pointer is
225 * updated; otherwise, the pointer is assumed to refer to an
226 * existing valid benchmark state.
227 */
228
c91413e6
MW
229void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
230 void *pctx, void *ctx)
e63124bc
MW
231{
232 struct tvec_benchctx *bc = ctx;
c91413e6
MW
233 const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
234 const struct tvec_env *subenv = be->env;
e63124bc 235 struct bench_timer *bt;
13ee7406 236 dstr d = DSTR_INIT;
e63124bc 237
67b5031e 238 /* Basic initialization. */
c91413e6 239 bc->be = be; bc->bst = 0; bc->subctx = 0;
e63124bc 240
67b5031e 241 /* Set up the benchmarking state if it hasn't been done before. */
13ee7406 242 if (be->bst && *be->bst)
c91413e6 243 bc->bst = *be->bst;
13ee7406
MW
244 else {
245 bt = bench_createtimer(0);
246 if (!bt)
247 { tvec_skipgroup(tv, "failed to create timer"); goto timer_failed; }
248 bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt);
249 *be->bst = bc->bst;
250 }
67b5031e 251
13ee7406
MW
252 /* If the timer isn't calibrated yet then do that now. */
253 if (!(bc->bst->f&BTF_CLB)) {
254 bc->bst->tm->ops->describe(bc->bst->tm, &d);
255 tvec_notice(tv, "calibrating timer `%s'...", d.buf);
256 if (bench_calibrate(bc->bst))
257 { tvec_skipgroup(tv, "failed to calibrate timer"); goto timer_failed; }
258 } else if (!(bc->bst->f&BTF_ANY))
259 { tvec_skipgroup(tv, "timer broken"); goto timer_failed; }
260
261 /* Save the default target time. */
e63124bc
MW
262 bc->dflt_target = bc->bst->target_s;
263
67b5031e 264 /* Initialize the subordinate environment. */
13ee7406 265end:
e63124bc 266 if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
c91413e6 267 if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
e63124bc 268
67b5031e 269 /* All done. */
13ee7406 270 dstr_destroy(&d);
c91413e6 271 return;
13ee7406
MW
272
273timer_failed:
274 if (!getenv("MLIB_BENCH_DEBUG"))
275 tvec_notice(tv, "set `MLIB_BENCH_DEBUG=t' in the environment "
276 "for more detail");
277 goto end;
e63124bc
MW
278}
279
814e42ff 280/* --- @tvec_benchfindvar@, @setvar@ --- *
67b5031e
MW
281 *
282 * Arguments: @struct tvec_state *tv@ = test vector state
283 * @const char *var@ = variable name to set
814e42ff
MW
284 * @const union tvec_regval *rv@ = register value
285 * @void **ctx_out@ = where to put the @setvar@ context
67b5031e
MW
286 * @void *ctx@ = context pointer
287 *
814e42ff
MW
288 * Returns: @tvec_benchfindvar@ returns a pointer to the variable
289 * definition, or null; @setvar@ returns zero on success or
290 * %$-1$% on error.
67b5031e 291 *
814e42ff
MW
292 * Use: Find a definition for a special variable. The following
293 * special variables are supported.
67b5031e 294 *
814e42ff 295 * * %|@target|% is the (approximate) duration to run
67b5031e
MW
296 * the benchmark.
297 *
298 * Unrecognized variables are passed to the subordinate
299 * environment, if there is one.
300 */
301
814e42ff
MW
302static int setvar(struct tvec_state *tv, const char *var,
303 const union tvec_regval *rv, void *ctx)
e63124bc
MW
304{
305 struct tvec_benchctx *bc = ctx;
e63124bc
MW
306
307 if (STRCMP(var, ==, "@target")) {
6e683a79 308 if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var));
814e42ff
MW
309 bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG;
310 } else assert("unknown var");
311 return (0);
312}
313
314static const struct tvec_vardef target_var =
d056fbdf 315 { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } };
814e42ff
MW
316
317const struct tvec_vardef *tvec_benchfindvar
318 (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
319{
320 struct tvec_benchctx *bc = ctx;
321 const struct tvec_benchenv *be = bc->be;
322 const struct tvec_env *subenv = be->env;
323
324 if (STRCMP(var, ==, "@target")) { *ctx_out = bc; return (&target_var); }
325 else if (subenv && subenv->findvar)
326 return (subenv->findvar(tv, var, ctx_out, bc->subctx));
327 else return (0);
e63124bc
MW
328}
329
67b5031e
MW
330/* --- @tvec_benchbefore@ --- *
331 *
332 * Arguments: @struct tvec_state *tv@ = test vector state
333 * @void *ctx@ = context pointer
334 *
c91413e6 335 * Returns: ---
67b5031e
MW
336 *
337 * Use: Invoke the subordinate environment's @before@ function to
338 * prepare for the benchmark.
339 */
340
c91413e6 341void tvec_benchbefore(struct tvec_state *tv, void *ctx)
e63124bc
MW
342{
343 struct tvec_benchctx *bc = ctx;
c91413e6
MW
344 const struct tvec_benchenv *be = bc->be;
345 const struct tvec_env *subenv = be->env;
e63124bc 346
c91413e6 347 if (subenv && subenv->before) subenv->before(tv, bc->subctx);
e63124bc
MW
348}
349
67b5031e
MW
350/* --- @tvec_benchrun@ --- *
351 *
352 * Arguments: @struct tvec_state *tv@ = test vector state
353 * @tvec_testfn *fn@ = test function to run
354 * @void *ctx@ = context pointer for the test function
355 *
356 * Returns: ---
357 *
358 * Use: Measures and reports the performance of a test function.
67b5031e 359 */
e63124bc
MW
360
361void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
362{
363 struct tvec_benchctx *bc = ctx;
c91413e6
MW
364 const struct tvec_benchenv *be = bc->be;
365 const struct tvec_env *subenv = be->env;
e63124bc
MW
366 const struct tvec_regdef *rd;
367 struct tvec_output *o = tv->output;
368 struct bench_timing tm;
369 struct benchrun r;
370 bench_fn *loopfn;
371 unsigned unit;
372 dstr d = DSTR_INIT;
373 double base;
374 unsigned f = 0;
375#define f_any 1u
376
67b5031e 377 /* Fill in the easy parts of the run context. */
e63124bc
MW
378 r.tv = tv; r.env = subenv; r.ctx = bc->subctx;
379 r.in = tv->in; r.out = tv->out; r.fn = fn;
380
67b5031e 381 /* Decide on the run function to select. */
c91413e6
MW
382 if (be->riter >= 0) {
383 r.n = &TVEC_REG(tv, in, be->riter)->v.u;
e63124bc
MW
384 loopfn = subenv && subenv->run ?
385 benchloop_inner_indirect : benchloop_inner_direct;
386 } else {
387 r.n = 0;
388 loopfn = subenv && subenv->run ?
389 benchloop_outer_indirect : benchloop_outer_direct;
390 }
391
67b5031e 392 /* Decide on the kind of unit and the base count. */
c91413e6
MW
393 base = be->niter;
394 if (be->rbuf < 0) unit = TVBU_OP;
395 else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
e63124bc 396
67b5031e 397 /* Construct a description of the test using the identifier registers. */
e63124bc
MW
398 for (rd = tv->test->regs; rd->name; rd++)
399 if (rd->f&TVRF_ID) {
400 if (f&f_any) dstr_puts(&d, ", ");
401 else f |= f_any;
402 dstr_putf(&d, "%s = ", rd->name);
403 rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd,
404 TVSF_COMPACT, &dstr_printops, &d);
405 }
6e683a79 406 DPUTZ(&d);
e63124bc 407
67b5031e 408 /* Run the benchmark. */
e63124bc 409 o->ops->bbench(o, d.buf, unit);
8d372122 410 if (bench_measure(bc->bst, &tm, base, loopfn, &r))
e63124bc
MW
411 o->ops->ebench(o, d.buf, unit, 0);
412 else
413 o->ops->ebench(o, d.buf, unit, &tm);
414
415 dstr_destroy(&d);
416
417#undef f_any
418}
419
c81c35df 420/* --- @tvec_benchafter@ --- *
67b5031e 421 *
c81c35df
MW
422 * Arguments: @struct tvec_state *tv@ = test vector state
423 * @void *ctx@ = context pointer
67b5031e
MW
424 *
425 * Returns: ---
426 *
c81c35df
MW
427 * Use: Invoke the subordinate environment's @after@ function to
428 * clean up after the benchmark.
67b5031e
MW
429 */
430
c81c35df 431void tvec_benchafter(struct tvec_state *tv, void *ctx)
e63124bc 432{
c81c35df
MW
433 struct tvec_benchctx *bc = ctx;
434 const struct tvec_benchenv *be = bc->be;
435 const struct tvec_env *subenv = be->env;
e63124bc 436
c81c35df 437 /* Restore the benchmark state's old target. */
13ee7406 438 if (bc->bst) bc->bst->target_s = bc->dflt_target;
c81c35df 439 bc->f &= ~TVBF_SETTRG;
e63124bc 440
c81c35df
MW
441 /* Pass the call on to the subsidiary environment. */
442 if (subenv && subenv->after) subenv->after(tv, bc->subctx);
443}
e63124bc 444
c81c35df
MW
445/* --- @tvec_benchteardown@ --- *
446 *
447 * Arguments: @struct tvec_state *tv@ = test vector state
448 * @void *ctx@ = context pointer
449 *
450 * Returns: ---
451 *
452 * Use: Tear down the benchmark environment.
453 */
e63124bc 454
c81c35df
MW
455void tvec_benchteardown(struct tvec_state *tv, void *ctx)
456{
457 struct tvec_benchctx *bc = ctx;
458 const struct tvec_benchenv *be;
459 const struct tvec_env *subenv;
e63124bc 460
c81c35df
MW
461 be = bc->be; subenv = be->env;
462
463 /* Tear down any subsidiary environment. */
464 if (subenv && subenv->teardown)
465 subenv->teardown(tv, bc->subctx);
466
467 /* If the benchmark state was temporary, then dispose of it. */
468 if (bc->bst) {
469 if (be->bst) bc->bst->target_s = bc->dflt_target;
470 else { bench_destroy(bc->bst); xfree(bc->bst); }
e63124bc
MW
471 }
472}
473
474/*----- That's all, folks -------------------------------------------------*/