Commit | Line | Data |
---|---|---|
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 | ||
35 | struct 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 | 46 | struct 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 | |
65 | static 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 = ¬hing; | |
80 | ||
81 | *x_inout = x; *unit_out = *u; | |
82 | } | |
83 | ||
c81c35df MW |
84 | /* --- @benchloop_...@ --- * |
85 | * | |
86 | * Arguments: @unsigned long n@ = iteration count | |
87 | * @void *ctx@ = benchmark running context | |
88 | * | |
89 | * Returns: --- | |
90 | * | |
91 | * Use: Run various kinds of benchmarking loops. | |
92 | * | |
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. | |
98 | * | |
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. | |
104 | */ | |
105 | ||
106 | static void benchloop_outer_direct(unsigned long n, void *ctx) | |
107 | { | |
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; | |
111 | ||
112 | while (n--) fn(in, out, tctx); | |
113 | } | |
114 | ||
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); } | |
117 | ||
118 | static void benchloop_outer_indirect(unsigned long n, void *ctx) | |
119 | { | |
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; | |
124 | ||
125 | while (n--) run(tv, fn, tctx); | |
126 | } | |
127 | ||
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); } | |
130 | ||
131 | /*----- Output utilities --------------------------------------------------*/ | |
132 | ||
133 | /* --- @tvec_benchreport@ --- * | |
134 | * | |
135 | * Arguments: @const struct gprintf_ops *gops@ = print operations | |
136 | * @void *go@ = print destination | |
137 | * @unsigned unit@ = the unit being measured (~TVBU_...@) | |
5c0f2e08 | 138 | * @unsigned style@ = output style (@TVSF_...@) |
c81c35df MW |
139 | * @const struct bench_timing *tm@ = the benchmark result |
140 | * | |
141 | * Returns: --- | |
142 | * | |
143 | * Use: Formats a report about the benchmark performance. This | |
144 | * function is intended to be called on by an output | |
145 | * @ebench@ function. | |
146 | */ | |
147 | ||
148 | void tvec_benchreport(const struct gprintf_ops *gops, void *go, | |
5c0f2e08 MW |
149 | unsigned unit, unsigned style, |
150 | const struct bench_timing *tm) | |
c81c35df MW |
151 | { |
152 | double scale, x, n = tm->n; | |
153 | const char *u, *what, *whats; | |
154 | ||
5c0f2e08 MW |
155 | if (!tm) { |
156 | if (style&TVSF_RAW) gprintf(gops, go, "FAILED"); | |
157 | else gprintf(gops, go, "benchmark FAILED"); | |
158 | return; | |
159 | } | |
c81c35df MW |
160 | |
161 | assert(tm->f&BTF_TIMEOK); | |
162 | ||
163 | switch (unit) { | |
164 | case TVBU_OP: | |
5c0f2e08 MW |
165 | if (style&TVSF_RAW) gprintf(gops, go, "ops=%.0f", n); |
166 | else gprintf(gops, go, "%.0f iterations ", n); | |
c81c35df MW |
167 | what = "op"; whats = "ops"; scale = 1000; |
168 | break; | |
169 | case TVBU_BYTE: | |
5c0f2e08 MW |
170 | if (style&TVSF_RAW) |
171 | gprintf(gops, go, "bytes=%.0f", n); | |
172 | else { | |
173 | x = n; | |
174 | normalize(&x, &u, 1024); gprintf(gops, go, " %.3f %sB ", x, u); | |
175 | } | |
c81c35df MW |
176 | what = whats = "B"; scale = 1024; |
177 | break; | |
178 | default: | |
179 | abort(); | |
180 | } | |
181 | ||
5c0f2e08 MW |
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); | |
185 | return; | |
186 | } | |
187 | ||
c81c35df MW |
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); | |
193 | } | |
194 | gprintf(gops, go, ": "); | |
195 | ||
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); | |
203 | } | |
204 | } | |
205 | ||
67b5031e MW |
206 | /*----- Benchmark environment scaffolding ---------------------------------*/ |
207 | ||
208 | /* --- @tvec_benchsetup@ --- * | |
209 | * | |
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 | |
214 | * | |
c91413e6 | 215 | * Returns: --- |
67b5031e MW |
216 | * |
217 | * Use: Initialize a benchmarking environment context. | |
218 | * | |
219 | * The environment description must really be a @struct | |
c91413e6 | 220 | * tvec_benchenv@. If the @bst@ slot is null, then a temporary |
67b5031e MW |
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. | |
227 | */ | |
228 | ||
c91413e6 MW |
229 | void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, |
230 | void *pctx, void *ctx) | |
e63124bc MW |
231 | { |
232 | struct tvec_benchctx *bc = ctx; | |
c91413e6 MW |
233 | const struct tvec_benchenv *be = (const struct tvec_benchenv *)env; |
234 | const struct tvec_env *subenv = be->env; | |
e63124bc | 235 | struct bench_timer *bt; |
13ee7406 | 236 | dstr d = DSTR_INIT; |
e63124bc | 237 | |
67b5031e | 238 | /* Basic initialization. */ |
c91413e6 | 239 | bc->be = be; bc->bst = 0; bc->subctx = 0; |
e63124bc | 240 | |
67b5031e | 241 | /* Set up the benchmarking state if it hasn't been done before. */ |
13ee7406 | 242 | if (be->bst && *be->bst) |
c91413e6 | 243 | bc->bst = *be->bst; |
13ee7406 MW |
244 | else { |
245 | bt = bench_createtimer(0); | |
246 | if (!bt) | |
247 | { tvec_skipgroup(tv, "failed to create timer"); goto timer_failed; } | |
248 | bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt); | |
249 | *be->bst = bc->bst; | |
250 | } | |
67b5031e | 251 | |
13ee7406 MW |
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; } | |
260 | ||
261 | /* Save the default target time. */ | |
e63124bc MW |
262 | bc->dflt_target = bc->bst->target_s; |
263 | ||
67b5031e | 264 | /* Initialize the subordinate environment. */ |
13ee7406 | 265 | end: |
e63124bc | 266 | if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz); |
c91413e6 | 267 | if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx); |
e63124bc | 268 | |
67b5031e | 269 | /* All done. */ |
13ee7406 | 270 | dstr_destroy(&d); |
c91413e6 | 271 | return; |
13ee7406 MW |
272 | |
273 | timer_failed: | |
274 | if (!getenv("MLIB_BENCH_DEBUG")) | |
275 | tvec_notice(tv, "set `MLIB_BENCH_DEBUG=t' in the environment " | |
276 | "for more detail"); | |
277 | goto end; | |
e63124bc MW |
278 | } |
279 | ||
814e42ff | 280 | /* --- @tvec_benchfindvar@, @setvar@ --- * |
67b5031e MW |
281 | * |
282 | * Arguments: @struct tvec_state *tv@ = test vector state | |
283 | * @const char *var@ = variable name to set | |
814e42ff MW |
284 | * @const union tvec_regval *rv@ = register value |
285 | * @void **ctx_out@ = where to put the @setvar@ context | |
67b5031e MW |
286 | * @void *ctx@ = context pointer |
287 | * | |
814e42ff MW |
288 | * Returns: @tvec_benchfindvar@ returns a pointer to the variable |
289 | * definition, or null; @setvar@ returns zero on success or | |
290 | * %$-1$% on error. | |
67b5031e | 291 | * |
814e42ff MW |
292 | * Use: Find a definition for a special variable. The following |
293 | * special variables are supported. | |
67b5031e | 294 | * |
814e42ff | 295 | * * %|@target|% is the (approximate) duration to run |
67b5031e MW |
296 | * the benchmark. |
297 | * | |
298 | * Unrecognized variables are passed to the subordinate | |
299 | * environment, if there is one. | |
300 | */ | |
301 | ||
814e42ff MW |
302 | static int setvar(struct tvec_state *tv, const char *var, |
303 | const union tvec_regval *rv, void *ctx) | |
e63124bc MW |
304 | { |
305 | struct tvec_benchctx *bc = ctx; | |
e63124bc MW |
306 | |
307 | if (STRCMP(var, ==, "@target")) { | |
6e683a79 | 308 | if (bc->f&TVBF_SETTRG) return (tvec_dupregerr(tv, var)); |
814e42ff MW |
309 | bc->bst->target_s = rv->f; bc->f |= TVBF_SETTRG; |
310 | } else assert("unknown var"); | |
311 | return (0); | |
312 | } | |
313 | ||
314 | static const struct tvec_vardef target_var = | |
d056fbdf | 315 | { sizeof(struct tvec_reg), setvar, { "@target", &tvty_duration, -1, 0 } }; |
814e42ff MW |
316 | |
317 | const struct tvec_vardef *tvec_benchfindvar | |
318 | (struct tvec_state *tv, const char *var, void **ctx_out, void *ctx) | |
319 | { | |
320 | struct tvec_benchctx *bc = ctx; | |
321 | const struct tvec_benchenv *be = bc->be; | |
322 | const struct tvec_env *subenv = be->env; | |
323 | ||
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)); | |
327 | else return (0); | |
e63124bc MW |
328 | } |
329 | ||
67b5031e MW |
330 | /* --- @tvec_benchbefore@ --- * |
331 | * | |
332 | * Arguments: @struct tvec_state *tv@ = test vector state | |
333 | * @void *ctx@ = context pointer | |
334 | * | |
c91413e6 | 335 | * Returns: --- |
67b5031e MW |
336 | * |
337 | * Use: Invoke the subordinate environment's @before@ function to | |
338 | * prepare for the benchmark. | |
339 | */ | |
340 | ||
c91413e6 | 341 | void tvec_benchbefore(struct tvec_state *tv, void *ctx) |
e63124bc MW |
342 | { |
343 | struct tvec_benchctx *bc = ctx; | |
c91413e6 MW |
344 | const struct tvec_benchenv *be = bc->be; |
345 | const struct tvec_env *subenv = be->env; | |
e63124bc | 346 | |
c91413e6 | 347 | if (subenv && subenv->before) subenv->before(tv, bc->subctx); |
e63124bc MW |
348 | } |
349 | ||
67b5031e MW |
350 | /* --- @tvec_benchrun@ --- * |
351 | * | |
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 | |
355 | * | |
356 | * Returns: --- | |
357 | * | |
358 | * Use: Measures and reports the performance of a test function. | |
67b5031e | 359 | */ |
e63124bc MW |
360 | |
361 | void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) | |
362 | { | |
363 | struct tvec_benchctx *bc = ctx; | |
c91413e6 MW |
364 | const struct tvec_benchenv *be = bc->be; |
365 | const struct tvec_env *subenv = be->env; | |
e63124bc MW |
366 | const struct tvec_regdef *rd; |
367 | struct tvec_output *o = tv->output; | |
368 | struct bench_timing tm; | |
369 | struct benchrun r; | |
370 | bench_fn *loopfn; | |
371 | unsigned unit; | |
372 | dstr d = DSTR_INIT; | |
373 | double base; | |
374 | unsigned f = 0; | |
375 | #define f_any 1u | |
376 | ||
67b5031e | 377 | /* Fill in the easy parts of the run context. */ |
e63124bc MW |
378 | r.tv = tv; r.env = subenv; r.ctx = bc->subctx; |
379 | r.in = tv->in; r.out = tv->out; r.fn = fn; | |
380 | ||
67b5031e | 381 | /* Decide on the run function to select. */ |
c91413e6 MW |
382 | if (be->riter >= 0) { |
383 | r.n = &TVEC_REG(tv, in, be->riter)->v.u; | |
e63124bc MW |
384 | loopfn = subenv && subenv->run ? |
385 | benchloop_inner_indirect : benchloop_inner_direct; | |
386 | } else { | |
387 | r.n = 0; | |
388 | loopfn = subenv && subenv->run ? | |
389 | benchloop_outer_indirect : benchloop_outer_direct; | |
390 | } | |
391 | ||
67b5031e | 392 | /* Decide on the kind of unit and the base count. */ |
c91413e6 MW |
393 | base = be->niter; |
394 | if (be->rbuf < 0) unit = TVBU_OP; | |
395 | else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; } | |
e63124bc | 396 | |
67b5031e | 397 | /* Construct a description of the test using the identifier registers. */ |
e63124bc MW |
398 | for (rd = tv->test->regs; rd->name; rd++) |
399 | if (rd->f&TVRF_ID) { | |
400 | if (f&f_any) dstr_puts(&d, ", "); | |
401 | else f |= f_any; | |
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); | |
405 | } | |
6e683a79 | 406 | DPUTZ(&d); |
e63124bc | 407 | |
67b5031e | 408 | /* Run the benchmark. */ |
e63124bc | 409 | o->ops->bbench(o, d.buf, unit); |
8d372122 | 410 | if (bench_measure(bc->bst, &tm, base, loopfn, &r)) |
e63124bc MW |
411 | o->ops->ebench(o, d.buf, unit, 0); |
412 | else | |
413 | o->ops->ebench(o, d.buf, unit, &tm); | |
414 | ||
415 | dstr_destroy(&d); | |
416 | ||
417 | #undef f_any | |
418 | } | |
419 | ||
c81c35df | 420 | /* --- @tvec_benchafter@ --- * |
67b5031e | 421 | * |
c81c35df MW |
422 | * Arguments: @struct tvec_state *tv@ = test vector state |
423 | * @void *ctx@ = context pointer | |
67b5031e MW |
424 | * |
425 | * Returns: --- | |
426 | * | |
c81c35df MW |
427 | * Use: Invoke the subordinate environment's @after@ function to |
428 | * clean up after the benchmark. | |
67b5031e MW |
429 | */ |
430 | ||
c81c35df | 431 | void tvec_benchafter(struct tvec_state *tv, void *ctx) |
e63124bc | 432 | { |
c81c35df MW |
433 | struct tvec_benchctx *bc = ctx; |
434 | const struct tvec_benchenv *be = bc->be; | |
435 | const struct tvec_env *subenv = be->env; | |
e63124bc | 436 | |
c81c35df | 437 | /* Restore the benchmark state's old target. */ |
13ee7406 | 438 | if (bc->bst) bc->bst->target_s = bc->dflt_target; |
c81c35df | 439 | bc->f &= ~TVBF_SETTRG; |
e63124bc | 440 | |
c81c35df MW |
441 | /* Pass the call on to the subsidiary environment. */ |
442 | if (subenv && subenv->after) subenv->after(tv, bc->subctx); | |
443 | } | |
e63124bc | 444 | |
c81c35df MW |
445 | /* --- @tvec_benchteardown@ --- * |
446 | * | |
447 | * Arguments: @struct tvec_state *tv@ = test vector state | |
448 | * @void *ctx@ = context pointer | |
449 | * | |
450 | * Returns: --- | |
451 | * | |
452 | * Use: Tear down the benchmark environment. | |
453 | */ | |
e63124bc | 454 | |
c81c35df MW |
455 | void tvec_benchteardown(struct tvec_state *tv, void *ctx) |
456 | { | |
457 | struct tvec_benchctx *bc = ctx; | |
458 | const struct tvec_benchenv *be; | |
459 | const struct tvec_env *subenv; | |
e63124bc | 460 | |
c81c35df MW |
461 | be = bc->be; subenv = be->env; |
462 | ||
463 | /* Tear down any subsidiary environment. */ | |
464 | if (subenv && subenv->teardown) | |
465 | subenv->teardown(tv, bc->subctx); | |
466 | ||
467 | /* If the benchmark state was temporary, then dispose of it. */ | |
468 | if (bc->bst) { | |
469 | if (be->bst) bc->bst->target_s = bc->dflt_target; | |
470 | else { bench_destroy(bc->bst); xfree(bc->bst); } | |
e63124bc MW |
471 | } |
472 | } | |
473 | ||
474 | /*----- That's all, folks -------------------------------------------------*/ |