chiark / gitweb /
@@@ tvec and tty 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)
265     bc->subctx = pool_alloc(tv->p_group, subenv->ctxsz);
266   if (subenv && subenv->setup)
267     subenv->setup(tv, subenv, bc, bc->subctx);
268 }
269
270 /* --- @tvec_benchfindvar@, @setvar@ --- *
271  *
272  * Arguments:   @struct tvec_state *tv@ = test vector state
273  *              @const char *var@ = variable name to set
274  *              @const union tvec_regval *rv@ = register value
275  *              @void **ctx_out@ = where to put the @setvar@ context
276  *              @void *ctx@ = context pointer
277  *
278  * Returns:     @tvec_benchfindvar@ returns a pointer to the variable
279  *              definition, or null; @setvar@ returns zero on success or
280  *              %$-1$% on error.
281  *
282  * Use:         Find a definition for a special variable.  The following
283  *              special variables are supported.
284  *
285  *                * %|@target|% is the (approximate) duration to run
286  *                  the benchmark.
287  *
288  *              Unrecognized variables are passed to the subordinate
289  *              environment, if there is one.
290  */
291
292 static int setvar(struct tvec_state *tv, const char *var,
293                   const union tvec_regval *rv, void *ctx)
294 {
295   struct tvec_benchctx *bc = ctx;
296
297   if (STRCMP(var, ==, "@target")) {
298     if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var));
299     bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG;
300   } else assert("unknown var");
301   return (0);
302 }
303
304 static const struct tvec_vardef target_var =
305   { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } };
306
307 const struct tvec_vardef *tvec_benchfindvar
308   (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx)
309 {
310   struct tvec_benchctx *bc = ctx;
311   const struct tvec_benchenv *be = bc->be;
312   const struct tvec_env *subenv = be->env;
313
314   if (STRCMP(var, ==, "@target")) { *ctx_out = bc; return (&target_var); }
315   else if (subenv && subenv->findvar)
316     return (subenv->findvar(tv, var, ctx_out, bc->subctx));
317   else return (0);
318 }
319
320 /* --- @tvec_benchbefore@ --- *
321  *
322  * Arguments:   @struct tvec_state *tv@ = test vector state
323  *              @void *ctx@ = context pointer
324  *
325  * Returns:     ---
326  *
327  * Use:         Invoke the subordinate environment's @before@ function to
328  *              prepare for the benchmark.
329  */
330
331 void tvec_benchbefore(struct tvec_state *tv, void *ctx)
332 {
333   struct tvec_benchctx *bc = ctx;
334   const struct tvec_benchenv *be = bc->be;
335   const struct tvec_env *subenv = be->env;
336
337   if (subenv && subenv->before) subenv->before(tv, bc->subctx);
338 }
339
340 /* --- @tvec_benchrun@ --- *
341  *
342  * Arguments:   @struct tvec_state *tv@ = test vector state
343  *              @tvec_testfn *fn@ = test function to run
344  *              @void *ctx@ = context pointer for the test function
345  *
346  * Returns:     ---
347  *
348  * Use:         Measures and reports the performance of a test function.
349  */
350
351 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
352 {
353   struct tvec_benchctx *bc = ctx;
354   const struct tvec_benchenv *be = bc->be;
355   const struct tvec_env *subenv = be->env;
356   struct tvec_output *o = tv->output;
357   struct tvec_fallbackoutput fo;
358   const struct tvec_benchoutops *bo;
359   BENCH_MEASURE_DECLS;
360   struct bench_timing t;
361   unsigned long *n;
362   unsigned unit;
363   double base;
364   int rc;
365
366   /* Decide on the kind of unit and the base count. */
367   base = be->niter;
368   if (be->rbuf < 0) unit = BTU_OP;
369   else { unit = BTU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
370
371   /* Find the output operations and report the start. */
372   bo = tvec_outputext(tv, &o, &fo,
373                       TVEC_BENCHOUTEXT, &tvec_benchoutputfallback);
374   bo->bbench(o, 0, unit);
375
376   /* Run the benchmark. */
377   if (be->riter >= 0) {
378     n = &TVEC_REG(tv, in, be->riter)->v.u;
379     if (subenv && subenv->run)
380       BENCH_MEASURE(bc->bst, rc, &t, base)
381         { *n = _bench_n; subenv->run(tv, fn, bc->subctx); }
382     else
383       BENCH_MEASURE(bc->bst, rc, &t, base)
384         { *n = _bench_n; fn(tv->in, tv->out, bc->subctx); }
385   } else {
386     if (subenv && subenv->run)
387       BENCH_MEASURE(bc->bst, rc, &t, base)
388         while (_bench_n--) subenv->run(tv, fn, bc->subctx);
389     else
390       BENCH_MEASURE(bc->bst, rc, &t, base)
391         while (_bench_n--) fn(tv->in, tv->out, bc->subctx);
392   }
393
394   /* Report the outcome. */
395   bo->ebench(o, 0, unit, rc ? 0 : &t);
396 }
397
398 /* --- @tvec_benchafter@ --- *
399  *
400  * Arguments:   @struct tvec_state *tv@ = test vector state
401  *              @void *ctx@ = context pointer
402  *
403  * Returns:     ---
404  *
405  * Use:         Invoke the subordinate environment's @after@ function to
406  *              clean up after the benchmark.
407  */
408
409 void tvec_benchafter(struct tvec_state *tv, void *ctx)
410 {
411   struct tvec_benchctx *bc = ctx;
412   const struct tvec_benchenv *be = bc->be;
413   const struct tvec_env *subenv = be->env;
414
415   /* Restore the benchmark state's old target. */
416   if (bc->bst) bc->bst->target_s = bc->dflt_target;
417   bc->f &= ~TVBF_SETTRG;
418
419   /* Pass the call on to the subsidiary environment. */
420   if (subenv && subenv->after) subenv->after(tv, bc->subctx);
421 }
422
423 /* --- @tvec_benchteardown@ --- *
424  *
425  * Arguments:   @struct tvec_state *tv@ = test vector state
426  *              @void *ctx@ = context pointer
427  *
428  * Returns:     ---
429  *
430  * Use:         Tear down the benchmark environment.
431  */
432
433 void tvec_benchteardown(struct tvec_state *tv, void *ctx)
434 {
435   struct tvec_benchctx *bc = ctx;
436   const struct tvec_benchenv *be;
437   const struct tvec_env *subenv;
438
439   be = bc->be; subenv = be->env;
440
441   /* Tear down any subsidiary environment. */
442   if (subenv && subenv->teardown) subenv->teardown(tv, bc->subctx);
443
444   /* If the benchmark state was temporary, then dispose of it. */
445   if (bc->bst) {
446     if (be->bst) bc->bst->target_s = bc->dflt_target;
447     else { bench_destroy(bc->bst); x_free(tv->a, bc->bst); }
448   }
449 }
450
451 /*----- That's all, folks -------------------------------------------------*/