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 { | |
36 | struct tvec_state *tv; | |
37 | const struct tvec_env *env; | |
38 | void *ctx; | |
39 | unsigned long *n; | |
40 | const struct tvec_reg *in; struct tvec_reg *out; | |
41 | tvec_testfn *fn; | |
42 | }; | |
43 | ||
44 | /*----- Global variables --------------------------------------------------*/ | |
45 | ||
46 | struct bench_state *tvec_benchstate; | |
47 | ||
48 | /*----- Benchmarking ------------------------------------------------------*/ | |
49 | ||
50 | static void normalize(double *x_inout, const char **unit_out, double scale) | |
51 | { | |
52 | static const char | |
53 | *const nothing = "", | |
54 | *const big[] = { "k", "M", "G", "T", "P", "E", 0 }, | |
55 | *const little[] = { "m", "ยต", "n", "p", "f", "a", 0 }; | |
56 | const char *const *u; | |
57 | double x = *x_inout; | |
58 | ||
59 | if (x < 1) | |
60 | for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale); | |
61 | else if (x >= scale) | |
62 | for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale); | |
63 | else | |
64 | u = ¬hing; | |
65 | ||
66 | *x_inout = x; *unit_out = *u; | |
67 | } | |
68 | ||
69 | int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, | |
70 | void *pctx, void *ctx) | |
71 | { | |
72 | struct tvec_benchctx *bc = ctx; | |
73 | const struct tvec_bench *b = (const struct tvec_bench *)env; | |
74 | const struct tvec_env *subenv = b->env; | |
75 | struct bench_timer *bt; | |
76 | ||
77 | bc->b = b; bc->bst = 0; bc->subctx = 0; | |
78 | ||
79 | if (!b->bst || !*b->bst) { | |
80 | bt = bench_createtimer(); if (!bt) goto fail_timer; | |
81 | bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt); | |
82 | if (b->bst) *b->bst = bc->bst; | |
83 | } else if (!(*b->bst)->tm) | |
84 | goto fail_timer; | |
85 | else | |
86 | bc->bst = *b->bst; | |
87 | bc->dflt_target = bc->bst->target_s; | |
88 | ||
89 | if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz); | |
90 | if (subenv && subenv->setup && subenv->setup(tv, subenv, bc, bc->subctx)) | |
91 | { xfree(bc->subctx); bc->subctx = 0; return (-1); } | |
92 | ||
93 | end: | |
94 | return (0); | |
95 | fail_timer: | |
96 | tvec_skipgroup(tv, "failed to create timer"); goto end; | |
97 | } | |
98 | ||
99 | int tvec_benchset(struct tvec_state *tv, const char *var, | |
100 | const struct tvec_env *env, void *ctx) | |
101 | { | |
102 | struct tvec_benchctx *bc = ctx; | |
103 | const struct tvec_bench *b = (const struct tvec_bench *)env; | |
104 | const struct tvec_env *subenv = b->env; | |
105 | union tvec_regval rv; | |
106 | static const struct tvec_floatinfo fi = { TVFF_NOMAX, 0.0, 0.0, 0.0 }; | |
107 | static const struct tvec_regdef rd = | |
108 | { "@target", -1, &tvty_float, 0, { &fi } }; | |
109 | ||
110 | if (STRCMP(var, ==, "@target")) { | |
111 | if (tvty_float.parse(&rv, &rd, tv)) return (-1); | |
112 | if (bc) bc->bst->target_s = rv.f; | |
113 | return (1); | |
114 | } else if (subenv && subenv->set) | |
115 | return (subenv->set(tv, var, subenv, bc->subctx)); | |
116 | else | |
117 | return (0); | |
118 | } | |
119 | ||
120 | int tvec_benchbefore(struct tvec_state *tv, void *ctx) | |
121 | { | |
122 | struct tvec_benchctx *bc = ctx; | |
123 | const struct tvec_bench *b = bc->b; | |
124 | const struct tvec_env *subenv = b->env; | |
125 | ||
126 | if (subenv && subenv->before) return (subenv->before(tv, bc->subctx)); | |
127 | else return (0); | |
128 | } | |
129 | ||
130 | void tvec_benchafter(struct tvec_state *tv, void *ctx) | |
131 | { | |
132 | struct tvec_benchctx *bc = ctx; | |
133 | const struct tvec_bench *b = bc->b; | |
134 | const struct tvec_env *subenv = b->env; | |
135 | ||
136 | bc->bst->target_s = bc->dflt_target; | |
137 | if (subenv && subenv->after) subenv->after(tv, bc->subctx); | |
138 | } | |
139 | ||
140 | void tvec_benchteardown(struct tvec_state *tv, void *ctx) | |
141 | { | |
142 | struct tvec_benchctx *bc = ctx; | |
143 | const struct tvec_bench *b; | |
144 | const struct tvec_env *subenv; | |
145 | ||
146 | if (!bc) return; | |
147 | b = bc->b; subenv = b->env; | |
148 | if (subenv && subenv->teardown && bc->subctx) | |
149 | subenv->teardown(tv, bc->subctx); | |
150 | if (bc->bst) { | |
151 | if (b->bst) bc->bst->target_s = bc->dflt_target; | |
152 | else { bench_destroy(bc->bst); xfree(bc->bst); } | |
153 | } | |
154 | } | |
155 | ||
156 | static void benchloop_outer_direct(unsigned long n, void *p) | |
157 | { | |
158 | struct benchrun *r = p; | |
159 | tvec_testfn *fn = r->fn; void *ctx = r->ctx; | |
160 | const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out; | |
161 | ||
162 | while (n--) fn(in, out, ctx); | |
163 | } | |
164 | ||
165 | static void benchloop_inner_direct(unsigned long n, void *p) | |
166 | { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); } | |
167 | ||
168 | static void benchloop_outer_indirect(unsigned long n, void *p) | |
169 | { | |
170 | struct benchrun *r = p; | |
171 | struct tvec_state *tv = r->tv; | |
172 | void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run; | |
173 | tvec_testfn *fn = r->fn; void *ctx = r->ctx; | |
174 | ||
175 | while (n--) run(tv, fn, ctx); | |
176 | } | |
177 | ||
178 | static void benchloop_inner_indirect(unsigned long n, void *p) | |
179 | { struct benchrun *r = p; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); } | |
180 | ||
181 | void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) | |
182 | { | |
183 | struct tvec_benchctx *bc = ctx; | |
184 | const struct tvec_bench *b = bc->b; | |
185 | const struct tvec_env *subenv = b->env; | |
186 | const struct tvec_regdef *rd; | |
187 | struct tvec_output *o = tv->output; | |
188 | struct bench_timing tm; | |
189 | struct benchrun r; | |
190 | bench_fn *loopfn; | |
191 | unsigned unit; | |
192 | dstr d = DSTR_INIT; | |
193 | double base; | |
194 | unsigned f = 0; | |
195 | #define f_any 1u | |
196 | ||
197 | r.tv = tv; r.env = subenv; r.ctx = bc->subctx; | |
198 | r.in = tv->in; r.out = tv->out; r.fn = fn; | |
199 | ||
200 | if (b->riter >= 0) { | |
201 | r.n = &TVEC_REG(tv, in, b->riter)->v.u; | |
202 | loopfn = subenv && subenv->run ? | |
203 | benchloop_inner_indirect : benchloop_inner_direct; | |
204 | } else { | |
205 | r.n = 0; | |
206 | loopfn = subenv && subenv->run ? | |
207 | benchloop_outer_indirect : benchloop_outer_direct; | |
208 | } | |
209 | ||
210 | base = b->niter; | |
211 | if (b->rbuf < 0) unit = TVBU_OP; | |
212 | else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, b->rbuf)->v.bytes.sz; } | |
213 | ||
214 | for (rd = tv->test->regs; rd->name; rd++) | |
215 | if (rd->f&TVRF_ID) { | |
216 | if (f&f_any) dstr_puts(&d, ", "); | |
217 | else f |= f_any; | |
218 | dstr_putf(&d, "%s = ", rd->name); | |
219 | rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, | |
220 | TVSF_COMPACT, &dstr_printops, &d); | |
221 | } | |
222 | ||
223 | o->ops->bbench(o, d.buf, unit); | |
224 | if (bench_measure(&tm, bc->bst, base, loopfn, &r)) | |
225 | o->ops->ebench(o, d.buf, unit, 0); | |
226 | else | |
227 | o->ops->ebench(o, d.buf, unit, &tm); | |
228 | ||
229 | dstr_destroy(&d); | |
230 | ||
231 | #undef f_any | |
232 | } | |
233 | ||
234 | void tvec_benchreport(const struct gprintf_ops *gops, void *go, | |
235 | unsigned unit, const struct bench_timing *tm) | |
236 | { | |
237 | double scale, x, n = tm->n; | |
238 | const char *u, *what, *whats; | |
239 | ||
240 | if (!tm) { gprintf(gops, go, "benchmark FAILED"); return; } | |
241 | ||
242 | assert(tm->f&BTF_TIMEOK); | |
243 | ||
244 | switch (unit) { | |
245 | case TVBU_OP: | |
246 | gprintf(gops, go, "%.0f iterations ", n); | |
247 | what = "op"; whats = "ops"; scale = 1000; | |
248 | break; | |
249 | case TVBU_BYTE: | |
250 | x = n; normalize(&x, &u, 1024); gprintf(gops, go, "%.3f %sB ", x, u); | |
251 | what = whats = "B"; scale = 1024; | |
252 | break; | |
253 | default: | |
254 | abort(); | |
255 | } | |
256 | ||
257 | x = tm->t; normalize(&x, &u, 1000); | |
258 | gprintf(gops, go, "in %.3f %ss", x, u); | |
259 | if (tm->f&BTF_CYOK) { | |
260 | x = tm->cy; normalize(&x, &u, 1000); | |
261 | gprintf(gops, go, " (%.3f %scy)", x, u); | |
262 | } | |
263 | gprintf(gops, go, ": "); | |
264 | ||
265 | x = n/tm->t; normalize(&x, &u, scale); | |
266 | gprintf(gops, go, "%.3f %s%s/s", x, u, whats); | |
267 | x = tm->t/n; normalize(&x, &u, 1000); | |
268 | gprintf(gops, go, ", %.3f %ss/%s", x, u, what); | |
269 | if (tm->f&BTF_CYOK) { | |
270 | x = tm->cy/n; normalize(&x, &u, 1000); | |
271 | gprintf(gops, go, " (%.3f %scy/%s)", x, u, what); | |
272 | } | |
273 | } | |
274 | ||
275 | /*----- That's all, folks -------------------------------------------------*/ |