@@@ fltfmt mess
[mLib] / test / tvec-bench.c
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
32 #include "tvec.h"
33 #include "tvec-bench.h"
34 #include "tvec-output.h"
35 #include "tvec-types.h"
36
37 /*----- Data structures ---------------------------------------------------*/
38
39 struct benchrun {
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 */
46 };
47
48 /*----- Global variables --------------------------------------------------*/
49
50 struct bench_state *tvec_benchstate; /* common benchmarking state */
51
52 /*----- Output utilities --------------------------------------------------*/
53
54 /* --- @tvec_benchreport@ --- *
55 *
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
61 *
62 * Returns: ---
63 *
64 * Use: Formats a report about the benchmark performance. This
65 * function is intended to be called on by an output
66 * @ebench@ function.
67 */
68
69 void tvec_benchreport(const struct gprintf_ops *gops, void *go,
70 unsigned unit, unsigned style,
71 const struct bench_timing *t)
72 {
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 }
87 }
88
89 /*----- Default output implementation -------------------------------------*/
90
91 /* --- @fallback_bbench@ --- *
92 *
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_...@)
97 *
98 * Returns: ---
99 *
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@ --- *
111 *
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
117 *
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.
125 */
126
127 static void fallback_ebench(struct tvec_output *o,
128 const char *desc, unsigned unit,
129 const struct bench_timing *t)
130 {
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
137
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, ": ");
152
153 /* Report the benchmark results. */
154 tvec_benchreport(&dstr_printops, &d, unit, 0, t);
155 DPUTZ(&d);
156
157 /* Write the result. */
158 tvec_info(tv, "%s", d.buf);
159 DDESTROY(&d);
160
161 #undef f_any
162 }
163
164 const struct tvec_benchoutops tvec_benchoutputfallback =
165 { fallback_bbench, fallback_ebench };
166
167 /*----- Benchmark environment scaffolding ---------------------------------*/
168
169 /* --- @tvec_benchprep@ --- *
170 *
171 * Arguments: @struct tvec_state *tv@ = test vector state
172 * @struct bench_state **bst_inout@ = benchmark state (updated)
173 * @unsigned f@ = calibration flags
174 *
175 * Returns: Zero on success, %$-1$% on failure.
176 *
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.
189 */
190
191 int tvec_benchprep(struct tvec_state *tv,
192 struct bench_state **bst_inout, unsigned f)
193 {
194 dstr d = DSTR_INIT;
195 struct bench_timer *bt;
196 struct bench_state *b;
197 int rc;
198
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;
207 }
208
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; }
217
218 rc = 0; goto end;
219
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;
227
228 end:
229 dstr_destroy(&d);
230 return (rc);
231 }
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 *
240 * Returns: ---
241 *
242 * Use: Initialize a benchmarking environment context.
243 *
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.
252 */
253
254 void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
255 void *pctx, void *ctx)
256 {
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;
260
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) bc->subctx = x_alloc(tv->a, subenv->ctxsz);
265 if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
266 }
267
268 /* --- @tvec_benchfindvar@, @setvar@ --- *
269 *
270 * Arguments: @struct tvec_state *tv@ = test vector state
271 * @const char *var@ = variable name to set
272 * @const union tvec_regval *rv@ = register value
273 * @void **ctx_out@ = where to put the @setvar@ context
274 * @void *ctx@ = context pointer
275 *
276 * Returns: @tvec_benchfindvar@ returns a pointer to the variable
277 * definition, or null; @setvar@ returns zero on success or
278 * %$-1$% on error.
279 *
280 * Use: Find a definition for a special variable. The following
281 * special variables are supported.
282 *
283 * * %|@target|% is the (approximate) duration to run
284 * the benchmark.
285 *
286 * Unrecognized variables are passed to the subordinate
287 * environment, if there is one.
288 */
289
290 static int setvar(struct tvec_state *tv, const char *var,
291 const union tvec_regval *rv, void *ctx)
292 {
293 struct tvec_benchctx *bc = ctx;
294
295 if (STRCMP(var, ==, "@target")) {
296 if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var));
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 =
303 { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } };
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);
316 }
317
318 /* --- @tvec_benchbefore@ --- *
319 *
320 * Arguments: @struct tvec_state *tv@ = test vector state
321 * @void *ctx@ = context pointer
322 *
323 * Returns: ---
324 *
325 * Use: Invoke the subordinate environment's @before@ function to
326 * prepare for the benchmark.
327 */
328
329 void tvec_benchbefore(struct tvec_state *tv, void *ctx)
330 {
331 struct tvec_benchctx *bc = ctx;
332 const struct tvec_benchenv *be = bc->be;
333 const struct tvec_env *subenv = be->env;
334
335 if (subenv && subenv->before) subenv->before(tv, bc->subctx);
336 }
337
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.
347 */
348
349 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
350 {
351 struct tvec_benchctx *bc = ctx;
352 const struct tvec_benchenv *be = bc->be;
353 const struct tvec_env *subenv = be->env;
354 struct tvec_output *o = tv->output;
355 struct tvec_fallbackoutput fo;
356 const struct tvec_benchoutops *bo;
357 BENCH_MEASURE_DECLS;
358 struct bench_timing t;
359 unsigned long *n;
360 unsigned unit;
361 double base;
362 int rc;
363
364 /* Decide on the kind of unit and the base count. */
365 base = be->niter;
366 if (be->rbuf < 0) unit = BTU_OP;
367 else { unit = BTU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
368
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);
373
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 }
391
392 /* Report the outcome. */
393 bo->ebench(o, 0, unit, rc ? 0 : &t);
394 }
395
396 /* --- @tvec_benchafter@ --- *
397 *
398 * Arguments: @struct tvec_state *tv@ = test vector state
399 * @void *ctx@ = context pointer
400 *
401 * Returns: ---
402 *
403 * Use: Invoke the subordinate environment's @after@ function to
404 * clean up after the benchmark.
405 */
406
407 void tvec_benchafter(struct tvec_state *tv, void *ctx)
408 {
409 struct tvec_benchctx *bc = ctx;
410 const struct tvec_benchenv *be = bc->be;
411 const struct tvec_env *subenv = be->env;
412
413 /* Restore the benchmark state's old target. */
414 if (bc->bst) bc->bst->target_s = bc->dflt_target;
415 bc->f &= ~TVBF_SETTRG;
416
417 /* Pass the call on to the subsidiary environment. */
418 if (subenv && subenv->after) subenv->after(tv, bc->subctx);
419 }
420
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 */
430
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;
436
437 be = bc->be; subenv = be->env;
438
439 /* Tear down any subsidiary environment. */
440 if (subenv && subenv->teardown) subenv->teardown(tv, bc->subctx);
441 if (bc->subctx) x_free(tv->a, bc->subctx);
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;
446 else { bench_destroy(bc->bst); x_free(tv->a, bc->bst); }
447 }
448 }
449
450 /*----- That's all, folks -------------------------------------------------*/