chiark / gitweb /
@@@ 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 -------------------------------------------------*/