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