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
95 * Use: Initialize a benchmarking environment context.
97 * The environment description must really be a @struct
98 * tvec_benchenv@. 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 void 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_benchenv
*be
= (const struct tvec_benchenv
*)env
;
112 const struct tvec_env
*subenv
= be
->env
;
113 struct bench_timer
*bt
;
115 /* Basic initialization. */
116 bc
->be
= be
; bc
->bst
= 0; bc
->subctx
= 0;
118 /* Set up the benchmarking state if it hasn't been done before. */
119 if (!be
->bst
|| !*be
->bst
) {
120 bt
= bench_createtimer(); if (!bt
) goto fail_timer
;
121 bc
->bst
= xmalloc(sizeof(*bc
->bst
)); bench_init(bc
->bst
, bt
);
122 if (be
->bst
) *be
->bst
= bc
->bst
;
123 } else if (!(*be
->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
);
139 tvec_skipgroup(tv
, "failed to create timer"); goto end
;
142 /* --- @tvec_benchset@ --- *
144 * Arguments: @struct tvec_state *tv@ = test vector state
145 * @const char *var@ = variable name to set
146 * @const struct tvec_env *env@ = environment description
147 * @void *ctx@ = context pointer
149 * Returns: Zero on success, @-1@ on failure.
151 * Use: Set a special variable. The following special variables are
154 * * %|@target|% is the (approximate) number of seconds to run
157 * Unrecognized variables are passed to the subordinate
158 * environment, if there is one.
161 int tvec_benchset(struct tvec_state
*tv
, const char *var
,
162 const struct tvec_env
*env
, void *ctx
)
164 struct tvec_benchctx
*bc
= ctx
;
165 const struct tvec_benchenv
*be
= (const struct tvec_benchenv
*)env
;
166 const struct tvec_env
*subenv
= be
->env
;
167 union tvec_regval rv
;
168 static const struct tvec_floatinfo fi
= { TVFF_NOMAX
, 0.0, 0.0, 0.0 };
169 static const struct tvec_regdef rd
=
170 { "@target", -1, &tvty_float
, 0, { &fi
} };
172 if (STRCMP(var
, ==, "@target")) {
173 if (tvty_float
.parse(&rv
, &rd
, tv
)) return (-1);
174 if (bc
) bc
->bst
->target_s
= rv
.f
;
176 } else if (subenv
&& subenv
->set
)
177 return (subenv
->set(tv
, var
, subenv
, bc
->subctx
));
182 /* --- @tvec_benchbefore@ --- *
184 * Arguments: @struct tvec_state *tv@ = test vector state
185 * @void *ctx@ = context pointer
189 * Use: Invoke the subordinate environment's @before@ function to
190 * prepare for the benchmark.
193 void tvec_benchbefore(struct tvec_state
*tv
, void *ctx
)
195 struct tvec_benchctx
*bc
= ctx
;
196 const struct tvec_benchenv
*be
= bc
->be
;
197 const struct tvec_env
*subenv
= be
->env
;
199 /* Just call the subsidiary environment. */
200 if (subenv
&& subenv
->before
) subenv
->before(tv
, bc
->subctx
);
203 /* --- @tvec_benchafter@ --- *
205 * Arguments: @struct tvec_state *tv@ = test vector state
206 * @void *ctx@ = context pointer
210 * Use: Invoke the subordinate environment's @after@ function to
211 * clean up after the benchmark.
214 void tvec_benchafter(struct tvec_state
*tv
, void *ctx
)
216 struct tvec_benchctx
*bc
= ctx
;
217 const struct tvec_benchenv
*be
= bc
->be
;
218 const struct tvec_env
*subenv
= be
->env
;
220 /* Restore the benchmark state's old target. */
221 bc
->bst
->target_s
= bc
->dflt_target
;
223 /* Pass the call on to the subsidiary environment. */
224 if (subenv
&& subenv
->after
) subenv
->after(tv
, bc
->subctx
);
227 /* --- @tvec_benchteardown@ --- *
229 * Arguments: @struct tvec_state *tv@ = test vector state
230 * @void *ctx@ = context pointer
234 * Use: Tear down the benchmark environment.
237 void tvec_benchteardown(struct tvec_state
*tv
, void *ctx
)
239 struct tvec_benchctx
*bc
= ctx
;
240 const struct tvec_benchenv
*be
;
241 const struct tvec_env
*subenv
;
244 be
= bc
->be
; subenv
= be
->env
;
246 /* Tear down any subsidiary environment. */
247 if (subenv
&& subenv
->teardown
&& bc
->subctx
)
248 subenv
->teardown(tv
, bc
->subctx
);
250 /* If the benchmark state was temporary, then dispose of it. */
252 if (be
->bst
) bc
->bst
->target_s
= bc
->dflt_target
;
253 else { bench_destroy(bc
->bst
); xfree(bc
->bst
); }
257 /*----- Measurement machinery ---------------------------------------------*/
259 /* --- @benchloop_...@ --- *
261 * Arguments: @unsigned long n@ = iteration count
262 * @void *ctx@ = benchmark running context
266 * Use: Run various kinds of benchmarking loops.
268 * * The @..._outer_...@ functions call the underlying
269 * function @n@ times in a loop; by contrast, the
270 * @..._inner_...@ functions set a register value to the
271 * chosen iteration count and expect the underlying function
272 * to perform the loop itself.
274 * * The @..._direct@ functions just call the underlying test
275 * function directly (though still through an `indirect
276 * jump' instruction); by contrast, the @..._indirect@
277 * functions invoke a subsidiary environment's @run@
278 * function, which adds additional overhead.
281 static void benchloop_outer_direct(unsigned long n
, void *ctx
)
283 struct benchrun
*r
= ctx
;
284 tvec_testfn
*fn
= r
->fn
; void *tctx
= r
->ctx
;
285 const struct tvec_reg
*in
= r
->in
; struct tvec_reg
*out
= r
->out
;
287 while (n
--) fn(in
, out
, tctx
);
290 static void benchloop_inner_direct(unsigned long n
, void *ctx
)
291 { struct benchrun
*r
= ctx
; *r
->n
= n
; r
->fn(r
->in
, r
->out
, r
->ctx
); }
293 static void benchloop_outer_indirect(unsigned long n
, void *ctx
)
295 struct benchrun
*r
= ctx
;
296 struct tvec_state
*tv
= r
->tv
;
297 void (*run
)(struct tvec_state
*, tvec_testfn
, void *) = r
->env
->run
;
298 tvec_testfn
*fn
= r
->fn
; void *tctx
= r
->ctx
;
300 while (n
--) run(tv
, fn
, tctx
);
303 static void benchloop_inner_indirect(unsigned long n
, void *ctx
)
304 { struct benchrun
*r
= ctx
; *r
->n
= n
; r
->env
->run(r
->tv
, r
->fn
, r
->ctx
); }
306 /* --- @tvec_benchrun@ --- *
308 * Arguments: @struct tvec_state *tv@ = test vector state
309 * @tvec_testfn *fn@ = test function to run
310 * @void *ctx@ = context pointer for the test function
314 * Use: Measures and reports the performance of a test function.
317 void tvec_benchrun(struct tvec_state
*tv
, tvec_testfn
*fn
, void *ctx
)
319 struct tvec_benchctx
*bc
= ctx
;
320 const struct tvec_benchenv
*be
= bc
->be
;
321 const struct tvec_env
*subenv
= be
->env
;
322 const struct tvec_regdef
*rd
;
323 struct tvec_output
*o
= tv
->output
;
324 struct bench_timing tm
;
333 /* Fill in the easy parts of the run context. */
334 r
.tv
= tv
; r
.env
= subenv
; r
.ctx
= bc
->subctx
;
335 r
.in
= tv
->in
; r
.out
= tv
->out
; r
.fn
= fn
;
337 /* Decide on the run function to select. */
338 if (be
->riter
>= 0) {
339 r
.n
= &TVEC_REG(tv
, in
, be
->riter
)->v
.u
;
340 loopfn
= subenv
&& subenv
->run ?
341 benchloop_inner_indirect
: benchloop_inner_direct
;
344 loopfn
= subenv
&& subenv
->run ?
345 benchloop_outer_indirect
: benchloop_outer_direct
;
348 /* Decide on the kind of unit and the base count. */
350 if (be
->rbuf
< 0) unit
= TVBU_OP
;
351 else { unit
= TVBU_BYTE
; base
*= TVEC_REG(tv
, in
, be
->rbuf
)->v
.bytes
.sz
; }
353 /* Construct a description of the test using the identifier registers. */
354 for (rd
= tv
->test
->regs
; rd
->name
; rd
++)
356 if (f
&f_any
) dstr_puts(&d
, ", ");
358 dstr_putf(&d
, "%s = ", rd
->name
);
359 rd
->ty
->dump(&TVEC_REG(tv
, in
, rd
->i
)->v
, rd
,
360 TVSF_COMPACT
, &dstr_printops
, &d
);
363 /* Run the benchmark. */
364 o
->ops
->bbench(o
, d
.buf
, unit
);
365 if (bench_measure(&tm
, bc
->bst
, base
, loopfn
, &r
))
366 o
->ops
->ebench(o
, d
.buf
, unit
, 0);
368 o
->ops
->ebench(o
, d
.buf
, unit
, &tm
);
375 /*----- Output utilities --------------------------------------------------*/
377 /* --- @tvec_benchreport@ --- *
379 * Arguments: @const struct gprintf_ops *gops@ = print operations
380 * @void *go@ = print destination
381 * @unsigned unit@ = the unit being measured (~TVBU_...@)
382 * @const struct bench_timing *tm@ = the benchmark result
386 * Use: Formats a report about the benchmark performance. This
387 * function is intended to be called on by an output
391 void tvec_benchreport(const struct gprintf_ops
*gops
, void *go
,
392 unsigned unit
, const struct bench_timing
*tm
)
394 double scale
, x
, n
= tm
->n
;
395 const char *u
, *what
, *whats
;
397 if (!tm
) { gprintf(gops
, go
, "benchmark FAILED"); return; }
399 assert(tm
->f
&BTF_TIMEOK
);
403 gprintf(gops
, go
, "%.0f iterations ", n
);
404 what
= "op"; whats
= "ops"; scale
= 1000;
407 x
= n
; normalize(&x
, &u
, 1024); gprintf(gops
, go
, "%.3f %sB ", x
, u
);
408 what
= whats
= "B"; scale
= 1024;
414 x
= tm
->t
; normalize(&x
, &u
, 1000);
415 gprintf(gops
, go
, "in %.3f %ss", x
, u
);
416 if (tm
->f
&BTF_CYOK
) {
417 x
= tm
->cy
; normalize(&x
, &u
, 1000);
418 gprintf(gops
, go
, " (%.3f %scy)", x
, u
);
420 gprintf(gops
, go
, ": ");
422 x
= n
/tm
->t
; normalize(&x
, &u
, scale
);
423 gprintf(gops
, go
, "%.3f %s%s/s", x
, u
, whats
);
424 x
= tm
->t
/n
; normalize(&x
, &u
, 1000);
425 gprintf(gops
, go
, ", %.3f %ss/%s", x
, u
, what
);
426 if (tm
->f
&BTF_CYOK
) {
427 x
= tm
->cy
/n
; normalize(&x
, &u
, 1000);
428 gprintf(gops
, go
, " (%.3f %scy/%s)", x
, u
, what
);
432 /*----- That's all, folks -------------------------------------------------*/