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