3 * Benchmarking in the test-vector framework
5 * (c) 2023 Straylight/Edgeware
8 /*----- Licensing notice --------------------------------------------------*
10 * This file is part of the mLib utilities library.
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.
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.
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,
28 /*----- Header files ------------------------------------------------------*/
33 /*----- Data structures ---------------------------------------------------*/
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 */
44 /*----- Global variables --------------------------------------------------*/
46 struct bench_state
*tvec_benchstate
; /* common benchmarking state */
48 /*----- Utilities ---------------------------------------------------------*/
50 /* --- @normalize@ --- *
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
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
65 static void normalize(double *x_inout
, const char **unit_out
, double scale
)
69 *const big
[] = { "k", "M", "G", "T", "P", "E", 0 },
70 *const little
[] = { "m", "ยต", "n", "p", "f", "a", 0 };
75 for (u
= little
, x
*= scale
; x
< 1 && u
[1]; u
++, x
*= scale
);
77 for (u
= big
, x
/= scale
; x
>= scale
&& u
[1]; u
++, x
/= scale
);
81 *x_inout
= x
; *unit_out
= *u
;
84 /* --- @benchloop_...@ --- *
86 * Arguments: @unsigned long n@ = iteration count
87 * @void *ctx@ = benchmark running context
91 * Use: Run various kinds of benchmarking loops.
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.
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.
106 static void benchloop_outer_direct(unsigned long n
, void *ctx
)
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
;
112 while (n
--) fn(in
, out
, tctx
);
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
); }
118 static void benchloop_outer_indirect(unsigned long n
, void *ctx
)
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
;
125 while (n
--) run(tv
, fn
, tctx
);
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
); }
131 /*----- Output utilities --------------------------------------------------*/
133 /* --- @tvec_benchreport@ --- *
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
143 * Use: Formats a report about the benchmark performance. This
144 * function is intended to be called on by an output
148 void tvec_benchreport(const struct gprintf_ops
*gops
, void *go
,
149 unsigned unit
, unsigned style
,
150 const struct bench_timing
*tm
)
152 double scale
, x
, n
= tm
->n
;
153 const char *u
, *what
, *whats
;
156 if (style
&TVSF_RAW
) gprintf(gops
, go
, "FAILED");
157 else gprintf(gops
, go
, "benchmark FAILED");
161 assert(tm
->f
&BTF_TIMEOK
);
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;
171 gprintf(gops
, go
, "bytes=%.0f", n
);
174 normalize(&x
, &u
, 1024); gprintf(gops
, go
, " %.3f %sB ", x
, u
);
176 what
= whats
= "B"; scale
= 1024;
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
);
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
);
194 gprintf(gops
, go
, ": ");
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
);
206 /*----- Benchmark environment scaffolding ---------------------------------*/
208 /* --- @tvec_benchsetup@ --- *
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
217 * Use: Initialize a benchmarking environment context.
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.
229 void tvec_benchsetup(struct tvec_state
*tv
, const struct tvec_env
*env
,
230 void *pctx
, void *ctx
)
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
;
238 /* Basic initialization. */
239 bc
->be
= be
; bc
->bst
= 0; bc
->subctx
= 0;
241 /* Set up the benchmarking state if it hasn't been done before. */
242 if (be
->bst
&& *be
->bst
)
245 bt
= bench_createtimer(0);
247 { tvec_skipgroup(tv
, "failed to create timer"); goto timer_failed
; }
248 bc
->bst
= xmalloc(sizeof(*bc
->bst
)); bench_init(bc
->bst
, bt
);
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
; }
261 /* Save the default target time. */
262 bc
->dflt_target
= bc
->bst
->target_s
;
264 /* Initialize the subordinate environment. */
266 if (subenv
&& subenv
->ctxsz
) bc
->subctx
= xmalloc(subenv
->ctxsz
);
267 if (subenv
&& subenv
->setup
) subenv
->setup(tv
, subenv
, bc
, bc
->subctx
);
274 if (!getenv("MLIB_BENCH_DEBUG"))
275 tvec_notice(tv
, "set `MLIB_BENCH_DEBUG=t' in the environment "
280 /* --- @tvec_benchfindvar@, @setvar@ --- *
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
288 * Returns: @tvec_benchfindvar@ returns a pointer to the variable
289 * definition, or null; @setvar@ returns zero on success or
292 * Use: Find a definition for a special variable. The following
293 * special variables are supported.
295 * * %|@target|% is the (approximate) duration to run
298 * Unrecognized variables are passed to the subordinate
299 * environment, if there is one.
302 static int setvar(struct tvec_state
*tv
, const char *var
,
303 const union tvec_regval
*rv
, void *ctx
)
305 struct tvec_benchctx
*bc
= ctx
;
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");
314 static const struct tvec_vardef target_var
=
315 { sizeof(struct tvec_reg
), setvar
, { "@target", &tvty_duration
, -1, 0 } };
317 const struct tvec_vardef
*tvec_benchfindvar
318 (struct tvec_state
*tv
, const char *var
, void **ctx_out
, void *ctx
)
320 struct tvec_benchctx
*bc
= ctx
;
321 const struct tvec_benchenv
*be
= bc
->be
;
322 const struct tvec_env
*subenv
= be
->env
;
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
));
330 /* --- @tvec_benchbefore@ --- *
332 * Arguments: @struct tvec_state *tv@ = test vector state
333 * @void *ctx@ = context pointer
337 * Use: Invoke the subordinate environment's @before@ function to
338 * prepare for the benchmark.
341 void tvec_benchbefore(struct tvec_state
*tv
, void *ctx
)
343 struct tvec_benchctx
*bc
= ctx
;
344 const struct tvec_benchenv
*be
= bc
->be
;
345 const struct tvec_env
*subenv
= be
->env
;
347 if (subenv
&& subenv
->before
) subenv
->before(tv
, bc
->subctx
);
350 /* --- @tvec_benchrun@ --- *
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
358 * Use: Measures and reports the performance of a test function.
361 void tvec_benchrun(struct tvec_state
*tv
, tvec_testfn
*fn
, void *ctx
)
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
;
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
;
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
;
388 loopfn
= subenv
&& subenv
->run ?
389 benchloop_outer_indirect
: benchloop_outer_direct
;
392 /* Decide on the kind of unit and the base count. */
394 if (be
->rbuf
< 0) unit
= TVBU_OP
;
395 else { unit
= TVBU_BYTE
; base
*= TVEC_REG(tv
, in
, be
->rbuf
)->v
.bytes
.sz
; }
397 /* Construct a description of the test using the identifier registers. */
398 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
400 if (f
&f_any
) dstr_puts(&d
, ", ");
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
);
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);
413 o
->ops
->ebench(o
, d
.buf
, unit
, &tm
);
420 /* --- @tvec_benchafter@ --- *
422 * Arguments: @struct tvec_state *tv@ = test vector state
423 * @void *ctx@ = context pointer
427 * Use: Invoke the subordinate environment's @after@ function to
428 * clean up after the benchmark.
431 void tvec_benchafter(struct tvec_state
*tv
, void *ctx
)
433 struct tvec_benchctx
*bc
= ctx
;
434 const struct tvec_benchenv
*be
= bc
->be
;
435 const struct tvec_env
*subenv
= be
->env
;
437 /* Restore the benchmark state's old target. */
438 if (bc
->bst
) bc
->bst
->target_s
= bc
->dflt_target
;
439 bc
->f
&= ~TVBF_SETTRG
;
441 /* Pass the call on to the subsidiary environment. */
442 if (subenv
&& subenv
->after
) subenv
->after(tv
, bc
->subctx
);
445 /* --- @tvec_benchteardown@ --- *
447 * Arguments: @struct tvec_state *tv@ = test vector state
448 * @void *ctx@ = context pointer
452 * Use: Tear down the benchmark environment.
455 void tvec_benchteardown(struct tvec_state
*tv
, void *ctx
)
457 struct tvec_benchctx
*bc
= ctx
;
458 const struct tvec_benchenv
*be
;
459 const struct tvec_env
*subenv
;
461 be
= bc
->be
; subenv
= be
->env
;
463 /* Tear down any subsidiary environment. */
464 if (subenv
&& subenv
->teardown
)
465 subenv
->teardown(tv
, bc
->subctx
);
467 /* If the benchmark state was temporary, then dispose of it. */
469 if (be
->bst
) bc
->bst
->target_s
= bc
->dflt_target
;
470 else { bench_destroy(bc
->bst
); xfree(bc
->bst
); }
474 /*----- That's all, folks -------------------------------------------------*/