chiark / gitweb /
22bd2e3ce570c9efb46d2c3fd06e4338a8b2cfaa
[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  *              @unsigned style@ = output style (@TVSF_...@)
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
148 void tvec_benchreport(const struct gprintf_ops *gops, void *go,
149                       unsigned unit, unsigned style,
150                       const struct bench_timing *tm)
151 {
152   double scale, x, n = tm->n;
153   const char *u, *what, *whats;
154
155   if (!tm) {
156     if (style&TVSF_RAW) gprintf(gops, go, "FAILED");
157     else gprintf(gops, go, "benchmark FAILED");
158     return;
159   }
160
161   assert(tm->f&BTF_TIMEOK);
162
163   switch (unit) {
164     case TVBU_OP:
165       if (style&TVSF_RAW) gprintf(gops, go, "ops=%.0f", n);
166       else gprintf(gops, go, "%.0f iterations ", n);
167       what = "op"; whats = "ops"; scale = 1000;
168       break;
169     case TVBU_BYTE:
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       }
176       what = whats = "B"; scale = 1024;
177       break;
178     default:
179       abort();
180   }
181
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
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
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  *
215  * Returns:     ---
216  *
217  * Use:         Initialize a benchmarking environment context.
218  *
219  *              The environment description must really be a @struct
220  *              tvec_benchenv@.  If the @bst@ slot is null, then a temporary
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
229 void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
230                      void *pctx, void *ctx)
231 {
232   struct tvec_benchctx *bc = ctx;
233   const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
234   const struct tvec_env *subenv = be->env;
235   struct bench_timer *bt;
236   dstr d = DSTR_INIT;
237
238   /* Basic initialization. */
239   bc->be = be; bc->bst = 0; bc->subctx = 0;
240
241   /* Set up the benchmarking state if it hasn't been done before. */
242   if (be->bst && *be->bst)
243     bc->bst = *be->bst;
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   }
251
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. */
262   bc->dflt_target = bc->bst->target_s;
263
264   /* Initialize the subordinate environment. */
265 end:
266   if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
267   if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
268
269   /* All done. */
270   dstr_destroy(&d);
271   return;
272
273 timer_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;
278 }
279
280 /* --- @tvec_benchfindvar@, @setvar@ --- *
281  *
282  * Arguments:   @struct tvec_state *tv@ = test vector state
283  *              @const char *var@ = variable name to set
284  *              @const union tvec_regval *rv@ = register value
285  *              @void **ctx_out@ = where to put the @setvar@ context
286  *              @void *ctx@ = context pointer
287  *
288  * Returns:     @tvec_benchfindvar@ returns a pointer to the variable
289  *              definition, or null; @setvar@ returns zero on success or
290  *              %$-1$% on error.
291  *
292  * Use:         Find a definition for a special variable.  The following
293  *              special variables are supported.
294  *
295  *                * %|@target|% is the (approximate) duration to run
296  *                  the benchmark.
297  *
298  *              Unrecognized variables are passed to the subordinate
299  *              environment, if there is one.
300  */
301
302 static int setvar(struct tvec_state *tv, const char *var,
303                   const union tvec_regval *rv, void *ctx)
304 {
305   struct tvec_benchctx *bc = ctx;
306
307   if (STRCMP(var, ==, "@target")) {
308     if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var));
309     bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG;
310   } else assert("unknown var");
311   return (0);
312 }
313
314 static const struct tvec_vardef target_var =
315   { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } };
316
317 const 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);
328 }
329
330 /* --- @tvec_benchbefore@ --- *
331  *
332  * Arguments:   @struct tvec_state *tv@ = test vector state
333  *              @void *ctx@ = context pointer
334  *
335  * Returns:     ---
336  *
337  * Use:         Invoke the subordinate environment's @before@ function to
338  *              prepare for the benchmark.
339  */
340
341 void tvec_benchbefore(struct tvec_state *tv, void *ctx)
342 {
343   struct tvec_benchctx *bc = ctx;
344   const struct tvec_benchenv *be = bc->be;
345   const struct tvec_env *subenv = be->env;
346
347   if (subenv && subenv->before) subenv->before(tv, bc->subctx);
348 }
349
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.
359  */
360
361 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
362 {
363   struct tvec_benchctx *bc = ctx;
364   const struct tvec_benchenv *be = bc->be;
365   const struct tvec_env *subenv = be->env;
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
377   /* Fill in the easy parts of the run context. */
378   r.tv = tv; r.env = subenv; r.ctx = bc->subctx;
379   r.in = tv->in; r.out = tv->out; r.fn = fn;
380
381   /* Decide on the run function to select. */
382   if (be->riter >= 0) {
383     r.n = &TVEC_REG(tv, in, be->riter)->v.u;
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
392   /* Decide on the kind of unit and the base count. */
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; }
396
397   /* Construct a description of the test using the identifier registers. */
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     }
406   DPUTZ(&d);
407
408   /* Run the benchmark. */
409   o->ops->bbench(o, d.buf, unit);
410   if (bench_measure(bc->bst, &tm, base, loopfn, &r))
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
420 /* --- @tvec_benchafter@ --- *
421  *
422  * Arguments:   @struct tvec_state *tv@ = test vector state
423  *              @void *ctx@ = context pointer
424  *
425  * Returns:     ---
426  *
427  * Use:         Invoke the subordinate environment's @after@ function to
428  *              clean up after the benchmark.
429  */
430
431 void tvec_benchafter(struct tvec_state *tv, void *ctx)
432 {
433   struct tvec_benchctx *bc = ctx;
434   const struct tvec_benchenv *be = bc->be;
435   const struct tvec_env *subenv = be->env;
436
437   /* Restore the benchmark state's old target. */
438   if (bc->bst) bc->bst->target_s = bc->dflt_target;
439   bc->f &= ~TVBF_SETTRG;
440
441   /* Pass the call on to the subsidiary environment. */
442   if (subenv && subenv->after) subenv->after(tv, bc->subctx);
443 }
444
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  */
454
455 void 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;
460
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); }
471   }
472 }
473
474 /*----- That's all, folks -------------------------------------------------*/