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