c81fd689fb9adadc784f73e9275da831986436cc
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 * @const struct bench_timing *tm@ = the benchmark result
142 * Use: Formats a report about the benchmark performance. This
143 * function is intended to be called on by an output
147 void tvec_benchreport(const struct gprintf_ops
*gops
, void *go
,
148 unsigned unit
, const struct bench_timing
*tm
)
150 double scale
, x
, n
= tm
->n
;
151 const char *u
, *what
, *whats
;
153 if (!tm
) { gprintf(gops
, go
, "benchmark FAILED"); return; }
155 assert(tm
->f
&BTF_TIMEOK
);
159 gprintf(gops
, go
, "%.0f iterations ", n
);
160 what
= "op"; whats
= "ops"; scale
= 1000;
163 x
= n
; normalize(&x
, &u
, 1024); gprintf(gops
, go
, "%.3f %sB ", x
, u
);
164 what
= whats
= "B"; scale
= 1024;
170 x
= tm
->t
; normalize(&x
, &u
, 1000);
171 gprintf(gops
, go
, "in %.3f %ss", x
, u
);
172 if (tm
->f
&BTF_CYOK
) {
173 x
= tm
->cy
; normalize(&x
, &u
, 1000);
174 gprintf(gops
, go
, " (%.3f %scy)", x
, u
);
176 gprintf(gops
, go
, ": ");
178 x
= n
/tm
->t
; normalize(&x
, &u
, scale
);
179 gprintf(gops
, go
, "%.3f %s%s/s", x
, u
, whats
);
180 x
= tm
->t
/n
; normalize(&x
, &u
, 1000);
181 gprintf(gops
, go
, ", %.3f %ss/%s", x
, u
, what
);
182 if (tm
->f
&BTF_CYOK
) {
183 x
= tm
->cy
/n
; normalize(&x
, &u
, 1000);
184 gprintf(gops
, go
, " (%.3f %scy/%s)", x
, u
, what
);
188 /*----- Benchmark environment scaffolding ---------------------------------*/
190 /* --- @tvec_benchsetup@ --- *
192 * Arguments: @struct tvec_state *tv@ = test vector state
193 * @const struct tvec_env *env@ = environment description
194 * @void *pctx@ = parent context (ignored)
195 * @void *ctx@ = context pointer to initialize
199 * Use: Initialize a benchmarking environment context.
201 * The environment description must really be a @struct
202 * tvec_benchenv@. If the @bst@ slot is null, then a temporary
203 * benchmark state is allocated for the current test group and
204 * released at the end. Otherwise, it must be the address of a
205 * pointer to a benchmark state: if the pointer is null, then a
206 * fresh state is allocated and initialized and the pointer is
207 * updated; otherwise, the pointer is assumed to refer to an
208 * existing valid benchmark state.
211 void tvec_benchsetup(struct tvec_state
*tv
, const struct tvec_env
*env
,
212 void *pctx
, void *ctx
)
214 struct tvec_benchctx
*bc
= ctx
;
215 const struct tvec_benchenv
*be
= (const struct tvec_benchenv
*)env
;
216 const struct tvec_env
*subenv
= be
->env
;
217 struct bench_timer
*bt
;
220 /* Basic initialization. */
221 bc
->be
= be
; bc
->bst
= 0; bc
->subctx
= 0;
223 /* Set up the benchmarking state if it hasn't been done before. */
224 if (be
->bst
&& *be
->bst
)
227 bt
= bench_createtimer(0);
229 { tvec_skipgroup(tv
, "failed to create timer"); goto timer_failed
; }
230 bc
->bst
= xmalloc(sizeof(*bc
->bst
)); bench_init(bc
->bst
, bt
);
234 /* If the timer isn't calibrated yet then do that now. */
235 if (!(bc
->bst
->f
&BTF_CLB
)) {
236 bc
->bst
->tm
->ops
->describe(bc
->bst
->tm
, &d
);
237 tvec_notice(tv
, "calibrating timer `%s'...", d
.buf
);
238 if (bench_calibrate(bc
->bst
))
239 { tvec_skipgroup(tv
, "failed to calibrate timer"); goto timer_failed
; }
240 } else if (!(bc
->bst
->f
&BTF_ANY
))
241 { tvec_skipgroup(tv
, "timer broken"); goto timer_failed
; }
243 /* Save the default target time. */
244 bc
->dflt_target
= bc
->bst
->target_s
;
246 /* Initialize the subordinate environment. */
248 if (subenv
&& subenv
->ctxsz
) bc
->subctx
= xmalloc(subenv
->ctxsz
);
249 if (subenv
&& subenv
->setup
) subenv
->setup(tv
, subenv
, bc
, bc
->subctx
);
256 if (!getenv("MLIB_BENCH_DEBUG"))
257 tvec_notice(tv
, "set `MLIB_BENCH_DEBUG=t' in the environment "
262 /* --- @tvec_benchfindvar@, @setvar@ --- *
264 * Arguments: @struct tvec_state *tv@ = test vector state
265 * @const char *var@ = variable name to set
266 * @const union tvec_regval *rv@ = register value
267 * @void **ctx_out@ = where to put the @setvar@ context
268 * @void *ctx@ = context pointer
270 * Returns: @tvec_benchfindvar@ returns a pointer to the variable
271 * definition, or null; @setvar@ returns zero on success or
274 * Use: Find a definition for a special variable. The following
275 * special variables are supported.
277 * * %|@target|% is the (approximate) duration to run
280 * Unrecognized variables are passed to the subordinate
281 * environment, if there is one.
284 static int setvar(struct tvec_state
*tv
, const char *var
,
285 const union tvec_regval
*rv
, void *ctx
)
287 struct tvec_benchctx
*bc
= ctx
;
289 if (STRCMP(var
, ==, "@target")) {
290 if (bc
->f
&TVBF_SETTRG
) return (tvec_dupreg(tv
, var
));
291 bc
->bst
->target_s
= rv
->f
; bc
->f
|= TVBF_SETTRG
;
292 } else assert("unknown var");
296 static const struct tvec_vardef target_var
=
297 { sizeof(struct tvec_reg
), setvar
, { "@target", -1, &tvty_duration
, 0 } };
299 const struct tvec_vardef
*tvec_benchfindvar
300 (struct tvec_state
*tv
, const char *var
, void **ctx_out
, void *ctx
)
302 struct tvec_benchctx
*bc
= ctx
;
303 const struct tvec_benchenv
*be
= bc
->be
;
304 const struct tvec_env
*subenv
= be
->env
;
306 if (STRCMP(var
, ==, "@target")) { *ctx_out
= bc
; return (&target_var
); }
307 else if (subenv
&& subenv
->findvar
)
308 return (subenv
->findvar(tv
, var
, ctx_out
, bc
->subctx
));
312 /* --- @tvec_benchbefore@ --- *
314 * Arguments: @struct tvec_state *tv@ = test vector state
315 * @void *ctx@ = context pointer
319 * Use: Invoke the subordinate environment's @before@ function to
320 * prepare for the benchmark.
323 void tvec_benchbefore(struct tvec_state
*tv
, void *ctx
)
325 struct tvec_benchctx
*bc
= ctx
;
326 const struct tvec_benchenv
*be
= bc
->be
;
327 const struct tvec_env
*subenv
= be
->env
;
329 if (subenv
&& subenv
->before
) subenv
->before(tv
, bc
->subctx
);
332 /* --- @tvec_benchrun@ --- *
334 * Arguments: @struct tvec_state *tv@ = test vector state
335 * @tvec_testfn *fn@ = test function to run
336 * @void *ctx@ = context pointer for the test function
340 * Use: Measures and reports the performance of a test function.
343 void tvec_benchrun(struct tvec_state
*tv
, tvec_testfn
*fn
, void *ctx
)
345 struct tvec_benchctx
*bc
= ctx
;
346 const struct tvec_benchenv
*be
= bc
->be
;
347 const struct tvec_env
*subenv
= be
->env
;
348 const struct tvec_regdef
*rd
;
349 struct tvec_output
*o
= tv
->output
;
350 struct bench_timing tm
;
359 /* Fill in the easy parts of the run context. */
360 r
.tv
= tv
; r
.env
= subenv
; r
.ctx
= bc
->subctx
;
361 r
.in
= tv
->in
; r
.out
= tv
->out
; r
.fn
= fn
;
363 /* Decide on the run function to select. */
364 if (be
->riter
>= 0) {
365 r
.n
= &TVEC_REG(tv
, in
, be
->riter
)->v
.u
;
366 loopfn
= subenv
&& subenv
->run ?
367 benchloop_inner_indirect
: benchloop_inner_direct
;
370 loopfn
= subenv
&& subenv
->run ?
371 benchloop_outer_indirect
: benchloop_outer_direct
;
374 /* Decide on the kind of unit and the base count. */
376 if (be
->rbuf
< 0) unit
= TVBU_OP
;
377 else { unit
= TVBU_BYTE
; base
*= TVEC_REG(tv
, in
, be
->rbuf
)->v
.bytes
.sz
; }
379 /* Construct a description of the test using the identifier registers. */
380 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
382 if (f
&f_any
) dstr_puts(&d
, ", ");
384 dstr_putf(&d
, "%s = ", rd
->name
);
385 rd
->ty
->dump(&TVEC_REG(tv
, in
, rd
->i
)->v
, rd
,
386 TVSF_COMPACT
, &dstr_printops
, &d
);
389 /* Run the benchmark. */
390 o
->ops
->bbench(o
, d
.buf
, unit
);
391 if (bench_measure(&tm
, bc
->bst
, base
, loopfn
, &r
))
392 o
->ops
->ebench(o
, d
.buf
, unit
, 0);
394 o
->ops
->ebench(o
, d
.buf
, unit
, &tm
);
401 /* --- @tvec_benchafter@ --- *
403 * Arguments: @struct tvec_state *tv@ = test vector state
404 * @void *ctx@ = context pointer
408 * Use: Invoke the subordinate environment's @after@ function to
409 * clean up after the benchmark.
412 void tvec_benchafter(struct tvec_state
*tv
, void *ctx
)
414 struct tvec_benchctx
*bc
= ctx
;
415 const struct tvec_benchenv
*be
= bc
->be
;
416 const struct tvec_env
*subenv
= be
->env
;
418 /* Restore the benchmark state's old target. */
419 if (bc
->bst
) bc
->bst
->target_s
= bc
->dflt_target
;
420 bc
->f
&= ~TVBF_SETTRG
;
422 /* Pass the call on to the subsidiary environment. */
423 if (subenv
&& subenv
->after
) subenv
->after(tv
, bc
->subctx
);
426 /* --- @tvec_benchteardown@ --- *
428 * Arguments: @struct tvec_state *tv@ = test vector state
429 * @void *ctx@ = context pointer
433 * Use: Tear down the benchmark environment.
436 void tvec_benchteardown(struct tvec_state
*tv
, void *ctx
)
438 struct tvec_benchctx
*bc
= ctx
;
439 const struct tvec_benchenv
*be
;
440 const struct tvec_env
*subenv
;
442 be
= bc
->be
; subenv
= be
->env
;
444 /* Tear down any subsidiary environment. */
445 if (subenv
&& subenv
->teardown
)
446 subenv
->teardown(tv
, bc
->subctx
);
448 /* If the benchmark state was temporary, then dispose of it. */
450 if (be
->bst
) bc
->bst
->target_s
= bc
->dflt_target
;
451 else { bench_destroy(bc
->bst
); xfree(bc
->bst
); }
455 /*----- That's all, folks -------------------------------------------------*/