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 /*----- Benchmark environment scaffolding ---------------------------------*/
86 /* --- @tvec_benchsetup@ --- *
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
93 * Returns: Zero on success, @-1@ on failure.
95 * Use: Initialize a benchmarking environment context.
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.
107 int tvec_benchsetup(struct tvec_state
*tv
, const struct tvec_env
*env
,
108 void *pctx
, void *ctx
)
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
;
115 /* Basic initialization. */
116 bc
->b
= b
; bc
->bst
= 0; bc
->subctx
= 0;
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
)
128 /* Set the default target time. */
129 bc
->dflt_target
= bc
->bst
->target_s
;
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); }
140 tvec_skipgroup(tv
, "failed to create timer"); goto end
;
143 /* --- @tvec_benchset@ --- *
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
150 * Returns: Zero on success, @-1@ on failure.
152 * Use: Set a special variable. The following special variables are
155 * * %|@target|% is the (approximate) number of seconds to run
158 * Unrecognized variables are passed to the subordinate
159 * environment, if there is one.
162 int tvec_benchset(struct tvec_state
*tv
, const char *var
,
163 const struct tvec_env
*env
, void *ctx
)
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
} };
173 if (STRCMP(var
, ==, "@target")) {
174 if (tvty_float
.parse(&rv
, &rd
, tv
)) return (-1);
175 if (bc
) bc
->bst
->target_s
= rv
.f
;
177 } else if (subenv
&& subenv
->set
)
178 return (subenv
->set(tv
, var
, subenv
, bc
->subctx
));
183 /* --- @tvec_benchbefore@ --- *
185 * Arguments: @struct tvec_state *tv@ = test vector state
186 * @void *ctx@ = context pointer
188 * Returns: Zero on success, @-1@ on failure.
190 * Use: Invoke the subordinate environment's @before@ function to
191 * prepare for the benchmark.
194 int tvec_benchbefore(struct tvec_state
*tv
, void *ctx
)
196 struct tvec_benchctx
*bc
= ctx
;
197 const struct tvec_bench
*b
= bc
->b
;
198 const struct tvec_env
*subenv
= b
->env
;
200 /* Just call the subsidiary environment. */
201 if (subenv
&& subenv
->before
) return (subenv
->before(tv
, bc
->subctx
));
205 /* --- @tvec_benchafter@ --- *
207 * Arguments: @struct tvec_state *tv@ = test vector state
208 * @void *ctx@ = context pointer
212 * Use: Invoke the subordinate environment's @after@ function to
213 * clean up after the benchmark.
216 void tvec_benchafter(struct tvec_state
*tv
, void *ctx
)
218 struct tvec_benchctx
*bc
= ctx
;
219 const struct tvec_bench
*b
= bc
->b
;
220 const struct tvec_env
*subenv
= b
->env
;
222 /* Restore the benchmark state's old target. */
223 bc
->bst
->target_s
= bc
->dflt_target
;
225 /* Pass the call on to the subsidiary environment. */
226 if (subenv
&& subenv
->after
) subenv
->after(tv
, bc
->subctx
);
229 /* --- @tvec_benchteardown@ --- *
231 * Arguments: @struct tvec_state *tv@ = test vector state
232 * @void *ctx@ = context pointer
236 * Use: Tear down the benchmark environment.
239 void tvec_benchteardown(struct tvec_state
*tv
, void *ctx
)
241 struct tvec_benchctx
*bc
= ctx
;
242 const struct tvec_bench
*b
;
243 const struct tvec_env
*subenv
;
246 b
= bc
->b
; subenv
= b
->env
;
248 /* Tear down any subsidiary environment. */
249 if (subenv
&& subenv
->teardown
&& bc
->subctx
)
250 subenv
->teardown(tv
, bc
->subctx
);
252 /* If the benchmark state was temporary, then dispose of it. */
254 if (b
->bst
) bc
->bst
->target_s
= bc
->dflt_target
;
255 else { bench_destroy(bc
->bst
); xfree(bc
->bst
); }
259 /*----- Measurement machinery ---------------------------------------------*/
261 /* --- @benchloop_...@ --- *
263 * Arguments: @unsigned long n@ = iteration count
264 * @void *ctx@ = benchmark running context
268 * Use: Run various kinds of benchmarking loops.
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.
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.
283 static void benchloop_outer_direct(unsigned long n
, void *ctx
)
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
;
289 while (n
--) fn(in
, out
, tctx
);
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
); }
295 static void benchloop_outer_indirect(unsigned long n
, void *ctx
)
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
;
302 while (n
--) run(tv
, fn
, tctx
);
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
); }
308 /* --- @tvec_benchrun@ --- *
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
316 * Use: Measures and reports the performance of a test function.
321 void tvec_benchrun(struct tvec_state
*tv
, tvec_testfn
*fn
, void *ctx
)
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
;
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
;
341 /* Decide on the run function to select. */
343 r
.n
= &TVEC_REG(tv
, in
, b
->riter
)->v
.u
;
344 loopfn
= subenv
&& subenv
->run ?
345 benchloop_inner_indirect
: benchloop_inner_direct
;
348 loopfn
= subenv
&& subenv
->run ?
349 benchloop_outer_indirect
: benchloop_outer_direct
;
352 /* Decide on the kind of unit and the base count. */
354 if (b
->rbuf
< 0) unit
= TVBU_OP
;
355 else { unit
= TVBU_BYTE
; base
*= TVEC_REG(tv
, in
, b
->rbuf
)->v
.bytes
.sz
; }
357 /* Construct a description of the test using the identifier registers. */
358 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
360 if (f
&f_any
) dstr_puts(&d
, ", ");
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
);
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);
372 o
->ops
->ebench(o
, d
.buf
, unit
, &tm
);
379 /*----- Output utilities --------------------------------------------------*/
381 /* --- @tvec_benchreport@ --- *
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
390 * Use: Formats a report about the benchmark performance. This
391 * function is intended to be called on by an output
395 void tvec_benchreport(const struct gprintf_ops
*gops
, void *go
,
396 unsigned unit
, const struct bench_timing
*tm
)
398 double scale
, x
, n
= tm
->n
;
399 const char *u
, *what
, *whats
;
401 if (!tm
) { gprintf(gops
, go
, "benchmark FAILED"); return; }
403 assert(tm
->f
&BTF_TIMEOK
);
407 gprintf(gops
, go
, "%.0f iterations ", n
);
408 what
= "op"; whats
= "ops"; scale
= 1000;
411 x
= n
; normalize(&x
, &u
, 1024); gprintf(gops
, go
, "%.3f %sB ", x
, u
);
412 what
= whats
= "B"; scale
= 1024;
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
);
424 gprintf(gops
, go
, ": ");
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
);
436 /*----- That's all, folks -------------------------------------------------*/