chiark / gitweb /
@@@ remote works?
[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 /*----- Benchmark environment scaffolding ---------------------------------*/
85
86 /* --- @tvec_benchsetup@ --- *
87  *
88  * Arguments:   @struct tvec_state *tv@ = test vector state
89  *              @const struct tvec_env *env@ = environment description
90  *              @void *pctx@ = parent context (ignored)
91  *              @void *ctx@ = context pointer to initialize
92  *
93  * Returns:     ---
94  *
95  * Use:         Initialize a benchmarking environment context.
96  *
97  *              The environment description must really be a @struct
98  *              tvec_benchenv@.  If the @bst@ slot is null, then a temporary
99  *              benchmark state is allocated for the current test group and
100  *              released at the end.  Otherwise, it must be the address of a
101  *              pointer to a benchmark state: if the pointer is null, then a
102  *              fresh state is allocated and initialized and the pointer is
103  *              updated; otherwise, the pointer is assumed to refer to an
104  *              existing valid benchmark state.
105  */
106
107 void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
108                      void *pctx, void *ctx)
109 {
110   struct tvec_benchctx *bc = ctx;
111   const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
112   const struct tvec_env *subenv = be->env;
113   struct bench_timer *bt;
114
115   /* Basic initialization. */
116   bc->be = be; bc->bst = 0; bc->subctx = 0;
117
118   /* Set up the benchmarking state if it hasn't been done before. */
119   if (!be->bst || !*be->bst) {
120     bt = bench_createtimer(); if (!bt) goto fail_timer;
121     bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt);
122     if (be->bst) *be->bst = bc->bst;
123   } else if (!(*be->bst)->tm)
124     goto fail_timer;
125   else
126     bc->bst = *be->bst;
127
128   /* Set the default target time. */
129   bc->dflt_target = bc->bst->target_s;
130
131   /* Initialize the subordinate environment. */
132   if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
133   if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
134
135   /* All done. */
136 end:
137   return;
138 fail_timer:
139   tvec_skipgroup(tv, "failed to create timer"); goto end;
140 }
141
142 /* --- @tvec_benchset@ --- *
143  *
144  * Arguments:   @struct tvec_state *tv@ = test vector state
145  *              @const char *var@ = variable name to set
146  *              @const struct tvec_env *env@ = environment description
147  *              @void *ctx@ = context pointer
148  *
149  * Returns:     Zero on success, @-1@ on failure.
150  *
151  * Use:         Set a special variable.  The following special variables are
152  *              supported.
153  *
154  *                * %|@target|% is the (approximate) number of seconds to run
155  *                  the benchmark.
156  *
157  *              Unrecognized variables are passed to the subordinate
158  *              environment, if there is one.
159  */
160
161 int tvec_benchset(struct tvec_state *tv, const char *var,
162                   const struct tvec_env *env, void *ctx)
163 {
164   struct tvec_benchctx *bc = ctx;
165   const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
166   const struct tvec_env *subenv = be->env;
167   union tvec_regval rv;
168   static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 };
169   static const struct tvec_regdef rd =
170     { "@target", -1, &tvty_float, 0, { &fi } };
171
172   if (STRCMP(var, ==, "@target")) {
173     if (tvty_float.parse(&rv, &rd, tv)) return (-1);
174     if (bc) bc->bst->target_s = rv.f;
175     return (1);
176   } else if (subenv && subenv->set)
177     return (subenv->set(tv, var, subenv, bc->subctx));
178   else
179     return (0);
180 }
181
182 /* --- @tvec_benchbefore@ --- *
183  *
184  * Arguments:   @struct tvec_state *tv@ = test vector state
185  *              @void *ctx@ = context pointer
186  *
187  * Returns:     ---
188  *
189  * Use:         Invoke the subordinate environment's @before@ function to
190  *              prepare for the benchmark.
191  */
192
193 void tvec_benchbefore(struct tvec_state *tv, void *ctx)
194 {
195   struct tvec_benchctx *bc = ctx;
196   const struct tvec_benchenv *be = bc->be;
197   const struct tvec_env *subenv = be->env;
198
199   /* Just call the subsidiary environment. */
200   if (subenv && subenv->before) subenv->before(tv, bc->subctx);
201 }
202
203 /* --- @tvec_benchafter@ --- *
204  *
205  * Arguments:   @struct tvec_state *tv@ = test vector state
206  *              @void *ctx@ = context pointer
207  *
208  * Returns:     ---
209  *
210  * Use:         Invoke the subordinate environment's @after@ function to
211  *              clean up after the benchmark.
212  */
213
214 void tvec_benchafter(struct tvec_state *tv, void *ctx)
215 {
216   struct tvec_benchctx *bc = ctx;
217   const struct tvec_benchenv *be = bc->be;
218   const struct tvec_env *subenv = be->env;
219
220   /* Restore the benchmark state's old target. */
221   bc->bst->target_s = bc->dflt_target;
222
223   /* Pass the call on to the subsidiary environment. */
224   if (subenv && subenv->after) subenv->after(tv, bc->subctx);
225 }
226
227 /* --- @tvec_benchteardown@ --- *
228  *
229  * Arguments:   @struct tvec_state *tv@ = test vector state
230  *              @void *ctx@ = context pointer
231  *
232  * Returns:     ---
233  *
234  * Use:         Tear down the benchmark environment.
235  */
236
237 void tvec_benchteardown(struct tvec_state *tv, void *ctx)
238 {
239   struct tvec_benchctx *bc = ctx;
240   const struct tvec_benchenv *be;
241   const struct tvec_env *subenv;
242
243   if (!bc) return;
244   be = bc->be; subenv = be->env;
245
246   /* Tear down any subsidiary environment. */
247   if (subenv && subenv->teardown && bc->subctx)
248     subenv->teardown(tv, bc->subctx);
249
250   /* If the benchmark state was temporary, then dispose of it. */
251   if (bc->bst) {
252     if (be->bst) bc->bst->target_s = bc->dflt_target;
253     else { bench_destroy(bc->bst); xfree(bc->bst); }
254   }
255 }
256
257 /*----- Measurement machinery ---------------------------------------------*/
258
259 /* --- @benchloop_...@ --- *
260  *
261  * Arguments:   @unsigned long n@ = iteration count
262  *              @void *ctx@ = benchmark running context
263  *
264  * Returns:     ---
265  *
266  * Use:         Run various kinds of benchmarking loops.
267  *
268  *                * The @..._outer_...@ functions call the underlying
269  *                  function @n@ times in a loop; by contrast, the
270  *                  @..._inner_...@ functions set a register value to the
271  *                  chosen iteration count and expect the underlying function
272  *                  to perform the loop itself.
273  *
274  *                * The @..._direct@ functions just call the underlying test
275  *                  function directly (though still through an `indirect
276  *                  jump' instruction); by contrast, the @..._indirect@
277  *                  functions invoke a subsidiary environment's @run@
278  *                  function, which adds additional overhead.
279  */
280
281 static void benchloop_outer_direct(unsigned long n, void *ctx)
282 {
283   struct benchrun *r = ctx;
284   tvec_testfn *fn = r->fn; void *tctx = r->ctx;
285   const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out;
286
287   while (n--) fn(in, out, tctx);
288 }
289
290 static void benchloop_inner_direct(unsigned long n, void *ctx)
291   { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); }
292
293 static void benchloop_outer_indirect(unsigned long n, void *ctx)
294 {
295   struct benchrun *r = ctx;
296   struct tvec_state *tv = r->tv;
297   void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run;
298   tvec_testfn *fn = r->fn; void *tctx = r->ctx;
299
300   while (n--) run(tv, fn, tctx);
301 }
302
303 static void benchloop_inner_indirect(unsigned long n, void *ctx)
304   { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
305
306 /* --- @tvec_benchrun@ --- *
307  *
308  * Arguments:   @struct tvec_state *tv@ = test vector state
309  *              @tvec_testfn *fn@ = test function to run
310  *              @void *ctx@ = context pointer for the test function
311  *
312  * Returns:     ---
313  *
314  * Use:         Measures and reports the performance of a test function.
315  */
316
317 void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
318 {
319   struct tvec_benchctx *bc = ctx;
320   const struct tvec_benchenv *be = bc->be;
321   const struct tvec_env *subenv = be->env;
322   const struct tvec_regdef *rd;
323   struct tvec_output *o = tv->output;
324   struct bench_timing tm;
325   struct benchrun r;
326   bench_fn *loopfn;
327   unsigned unit;
328   dstr d = DSTR_INIT;
329   double base;
330   unsigned f = 0;
331 #define f_any 1u
332
333   /* Fill in the easy parts of the run context. */
334   r.tv = tv; r.env = subenv; r.ctx = bc->subctx;
335   r.in = tv->in; r.out = tv->out; r.fn = fn;
336
337   /* Decide on the run function to select. */
338   if (be->riter >= 0) {
339     r.n = &TVEC_REG(tv, in, be->riter)->v.u;
340     loopfn = subenv && subenv->run ?
341       benchloop_inner_indirect : benchloop_inner_direct;
342   } else {
343     r.n = 0;
344     loopfn = subenv && subenv->run ?
345       benchloop_outer_indirect : benchloop_outer_direct;
346   }
347
348   /* Decide on the kind of unit and the base count. */
349   base = be->niter;
350   if (be->rbuf < 0) unit = TVBU_OP;
351   else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
352
353   /* Construct a description of the test using the identifier registers. */
354   for (rd = tv->test->regs; rd->name; rd++)
355     if (rd->f&TVRF_ID) {
356       if (f&f_any) dstr_puts(&d, ", ");
357       else f |= f_any;
358       dstr_putf(&d, "%s = ", rd->name);
359       rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd,
360                    TVSF_COMPACT, &dstr_printops, &d);
361     }
362
363   /* Run the benchmark. */
364   o->ops->bbench(o, d.buf, unit);
365   if (bench_measure(&tm, bc->bst, base, loopfn, &r))
366     o->ops->ebench(o, d.buf, unit, 0);
367   else
368     o->ops->ebench(o, d.buf, unit, &tm);
369
370   dstr_destroy(&d);
371
372 #undef f_any
373 }
374
375 /*----- Output utilities --------------------------------------------------*/
376
377 /* --- @tvec_benchreport@ --- *
378  *
379  * Arguments:   @const struct gprintf_ops *gops@ = print operations
380  *              @void *go@ = print destination
381  *              @unsigned unit@ = the unit being measured (~TVBU_...@)
382  *              @const struct bench_timing *tm@ = the benchmark result
383  *
384  * Returns:     ---
385  *
386  * Use:         Formats a report about the benchmark performance.  This
387  *              function is intended to be called on by an output
388  *              @ebench@ function.
389  */
390
391 void tvec_benchreport(const struct gprintf_ops *gops, void *go,
392                       unsigned unit, const struct bench_timing *tm)
393 {
394   double scale, x, n = tm->n;
395   const char *u, *what, *whats;
396
397   if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; }
398
399   assert(tm->f&BTF_TIMEOK);
400
401   switch (unit) {
402     case TVBU_OP:
403       gprintf(gops, go, "%.0f iterations ", n);
404       what = "op"; whats = "ops"; scale = 1000;
405       break;
406     case TVBU_BYTE:
407       x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u);
408       what = whats = "B"; scale = 1024;
409       break;
410     default:
411       abort();
412   }
413
414   x = tm->t; normalize(&x, &u, 1000);
415   gprintf(gops, go, "in %.3f %ss", x, u);
416   if (tm->f&BTF_CYOK) {
417     x = tm->cy; normalize(&x, &u, 1000);
418     gprintf(gops, go, " (%.3f %scy)", x, u);
419   }
420   gprintf(gops, go, ": ");
421
422   x = n/tm->t; normalize(&x, &u, scale);
423   gprintf(gops, go, "%.3f %s%s/s", x, u, whats);
424   x = tm->t/n; normalize(&x, &u, 1000);
425   gprintf(gops, go, ", %.3f %ss/%s", x, u, what);
426   if (tm->f&BTF_CYOK) {
427     x = tm->cy/n; normalize(&x, &u, 1000);
428     gprintf(gops, go, " (%.3f %scy/%s)", x, u, what);
429   }
430 }
431
432 /*----- That's all, folks -------------------------------------------------*/