/*----- Header files ------------------------------------------------------*/
#include "bench.h"
+
#include "tvec.h"
+#include "tvec-bench.h"
+#include "tvec-output.h"
+#include "tvec-types.h"
/*----- Data structures ---------------------------------------------------*/
struct bench_state *tvec_benchstate; /* common benchmarking state */
-/*----- Utilities ---------------------------------------------------------*/
+/*----- Output utilities --------------------------------------------------*/
-/* --- @normalize@ --- *
+/* --- @tvec_benchreport@ --- *
*
- * Arguments: @double *x_inout@ = address of a value to normalize
- * @const char **unit_out@ = address to store unit prefix
- * @double scale@ = scale factor for unit steps
+ * Arguments: @const struct gprintf_ops *gops@ = print operations
+ * @void *go@ = print destination
+ * @unsigned unit@ = the unit being measured (@TVBU_...@)
+ * @unsigned style@ = output style (@TVSF_...@)
+ * @const struct bench_timing *t@ = the benchmark result
*
* Returns: ---
*
- * Use: Adjust @*x_inout@ by a power of @scale@, and set @*unit_out@
- * so that printing the two reflects the original value with an
- * appropriate SI unit scaling. The @scale@ should be 1024 for
- * binary quantities, most notably memory sizes, or 1000 for
- * other quantities.
+ * Use: Formats a report about the benchmark performance. This
+ * function is intended to be called on by an output
+ * @ebench@ function.
*/
-static void normalize(double *x_inout, const char **unit_out, double scale)
+void tvec_benchreport(const struct gprintf_ops *gops, void *go,
+ unsigned unit, unsigned style,
+ const struct bench_timing *t)
{
- static const char
- *const nothing = "",
- *const big[] = { "k", "M", "G", "T", "P", "E", 0 },
- *const little[] = { "m", "ยต", "n", "p", "f", "a", 0 };
- const char *const *u;
- double x = *x_inout;
-
- if (x < 1)
- for (u = little, x *= scale; x < 1 && u[1]; u++, x *= scale);
- else if (x >= scale)
- for (u = big, x /= scale; x >= scale && u[1]; u++, x /= scale);
- else
- u = ¬hing;
-
- *x_inout = x; *unit_out = *u;
+ if (!t) {
+ if (style&TVSF_RAW) gprintf(gops, go, "FAILED");
+ else gprintf(gops, go, "benchmark FAILED");
+ } else if (!(style&TVSF_RAW))
+ bench_report(gops, go, unit, t);
+ else {
+ switch (unit) {
+ case BTU_OP: gprintf(gops, go, "ops=%.0f", t->n); break;
+ case BTU_BYTE: gprintf(gops, go, "bytes=%.0f", t->n); break;
+ default: assert(0);
+ }
+ gprintf(gops, go, " sec=%.6g", t->t);
+ if (t->f&BTF_CYOK) gprintf(gops, go, " cy=%.0f", t->cy);
+ }
}
-/* --- @benchloop_...@ --- *
+/*----- Default output implementation -------------------------------------*/
+
+/* --- @fallback_bbench@ --- *
*
- * Arguments: @unsigned long n@ = iteration count
- * @void *ctx@ = benchmark running context
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct fallback_output@
+ * @const char *desc@ = adhoc test description
+ * @unsigned unit@ = measurement unit (@TVBU_...@)
*
* Returns: ---
*
- * Use: Run various kinds of benchmarking loops.
+ * Use: Report that a benchmark has started.
+ *
+ * The fallback implementation does nothing here. All of the
+ * reporting happens in @fallback_ebench@.
+ */
+
+static void fallback_bbench(struct tvec_output *o,
+ const char *desc, unsigned unit)
+ { ; }
+
+/* --- @fallback_ebench@ --- *
*
- * * The @..._outer_...@ functions call the underlying
- * function @n@ times in a loop; by contrast, the
- * @..._inner_...@ functions set a register value to the
- * chosen iteration count and expect the underlying function
- * to perform the loop itself.
+ * Arguments: @struct tvec_output *o@ = output sink, secretly a
+ * @struct fallback_output@
+ * @const char *desc@ = adhoc test description
+ * @unsigned unit@ = measurement unit (@BTU_...@)
+ * @const struct bench_timing *t@ = measurement
*
- * * The @..._direct@ functions just call the underlying test
- * function directly (though still through an `indirect
- * jump' instruction); by contrast, the @..._indirect@
- * functions invoke a subsidiary environment's @run@
- * function, which adds additional overhead.
+ * Returns: ---
+ *
+ * Use: Report a benchmark's results
+ *
+ * The fallback implementation just delegates to the default
+ * benchmark reporting to produce a line written through the
+ * standard @report@ output operation.
*/
-static void benchloop_outer_direct(unsigned long n, void *ctx)
+static void fallback_ebench(struct tvec_output *o,
+ const char *desc, unsigned unit,
+ const struct bench_timing *t)
{
- struct benchrun *r = ctx;
- tvec_testfn *fn = r->fn; void *tctx = r->ctx;
- const struct tvec_reg *in = r->in; struct tvec_reg *out = r->out;
+ struct tvec_fallbackoutput *fo = (struct tvec_fallbackoutput *)o;
+ struct tvec_state *tv = fo->tv;
+ const struct tvec_regdef *rd;
+ dstr d = DSTR_INIT;
+ unsigned f = 0;
+#define f_any 1u
- while (n--) fn(in, out, tctx);
-}
+ /* Build the identification string. */
+ dstr_putf(&d, "%s ", tv->test->name);
+ if (desc)
+ DPUTS(&d, desc);
+ else
+ for (rd = tv->test->regs; rd->name; rd++)
+ if (rd->f&TVRF_ID) {
+ if (f&f_any) DPUTS(&d, ", ");
+ else f |= f_any;
+ dstr_putf(&d, "%s = ", rd->name);
+ rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd, TVSF_COMPACT,
+ &dstr_printops, &d);
+ }
+ DPUTS(&d, ": ");
-static void benchloop_inner_direct(unsigned long n, void *ctx)
- { struct benchrun *r = ctx; *r->n = n; r->fn(r->in, r->out, r->ctx); }
+ /* Report the benchmark results. */
+ tvec_benchreport(&dstr_printops, &d, unit, 0, t);
+ DPUTZ(&d);
-static void benchloop_outer_indirect(unsigned long n, void *ctx)
-{
- struct benchrun *r = ctx;
- struct tvec_state *tv = r->tv;
- void (*run)(struct tvec_state *, tvec_testfn, void *) = r->env->run;
- tvec_testfn *fn = r->fn; void *tctx = r->ctx;
+ /* Write the result. */
+ tvec_info(tv, "%s", d.buf);
+ DDESTROY(&d);
- while (n--) run(tv, fn, tctx);
+#undef f_any
}
-static void benchloop_inner_indirect(unsigned long n, void *ctx)
- { struct benchrun *r = ctx; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); }
+const struct tvec_benchoutops tvec_benchoutputfallback =
+ { fallback_bbench, fallback_ebench };
-/*----- Output utilities --------------------------------------------------*/
+/*----- Benchmark environment scaffolding ---------------------------------*/
-/* --- @tvec_benchreport@ --- *
+/* --- @tvec_benchprep@ --- *
*
- * Arguments: @const struct gprintf_ops *gops@ = print operations
- * @void *go@ = print destination
- * @unsigned unit@ = the unit being measured (~TVBU_...@)
- * @unsigned style@ = output style (@TVSF_...@)
- * @const struct bench_timing *tm@ = the benchmark result
+ * Arguments: @struct tvec_state *tv@ = test vector state
+ * @struct bench_state **bst_inout@ = benchmark state (updated)
+ * @unsigned f@ = calibration flags
*
- * Returns: ---
+ * Returns: Zero on success, %$-1$% on failure.
*
- * Use: Formats a report about the benchmark performance. This
- * function is intended to be called on by an output
- * @ebench@ function.
+ * Use: If @*bst_inout@ is null then allocate and initialize a fresh
+ * benchmark state with a default timer, and @*bst_inout@ is
+ * updated to point to the fresh state. The storage for the
+ * state was allocated using the test vector state's arena.
+ *
+ * If the benchmark state hasn't been calibrated, then this is
+ * done, passing @f@ to @bench_calibrate@.
+ *
+ * On failure, the test group is skipped, reporting a suitable
+ * message, and %$-1$% is returned. If a fresh benchmark state
+ * was allocated, but calibration failed, the state is
+ * %%\emph{not}%% released.
*/
-void tvec_benchreport(const struct gprintf_ops *gops, void *go,
- unsigned unit, unsigned style,
- const struct bench_timing *tm)
+int tvec_benchprep(struct tvec_state *tv,
+ struct bench_state **bst_inout, unsigned f)
{
- double scale, x, n = tm->n;
- const char *u, *what, *whats;
+ dstr d = DSTR_INIT;
+ struct bench_timer *bt;
+ struct bench_state *b;
+ int rc;
- if (!tm) {
- if (style&TVSF_RAW) gprintf(gops, go, "FAILED");
- else gprintf(gops, go, "benchmark FAILED");
- return;
+ /* Set up the benchmarking state if it hasn't been done before. */
+ if (*bst_inout)
+ b = *bst_inout;
+ else {
+ bt = bench_createtimer(0);
+ if (!bt)
+ { tvec_skipgroup(tv, "failed to create timer"); goto timer_failed; }
+ X_NEW(b, tv->a); bench_init(b, bt); *bst_inout = b;
}
- assert(tm->f&BTF_TIMEOK);
-
- switch (unit) {
- case TVBU_OP:
- if (style&TVSF_RAW) gprintf(gops, go, "ops=%.0f", n);
- else gprintf(gops, go, "%.0f iterations ", n);
- what = "op"; whats = "ops"; scale = 1000;
- break;
- case TVBU_BYTE:
- if (style&TVSF_RAW)
- gprintf(gops, go, "bytes=%.0f", n);
- else {
- x = n;
- normalize(&x, &u, 1024); gprintf(gops, go, " %.3f %sB ", x, u);
- }
- what = whats = "B"; scale = 1024;
- break;
- default:
- abort();
- }
+ /* If the timer isn't calibrated yet then do that now. */
+ if (!(b->f&BTF_CLB)) {
+ b->tm->ops->describe(b->tm, &d);
+ tvec_notice(tv, "calibrating timer `%s'...", d.buf);
+ if (bench_calibrate(b, f))
+ { tvec_skipgroup(tv, "failed to calibrate timer"); goto timer_failed; }
+ } else if (!(b->f&BTF_ANY))
+ { tvec_skipgroup(tv, "timer broken"); goto timer_failed; }
- if (style&TVSF_RAW) {
- gprintf(gops, go, " sec=%.6g", tm->t);
- if (tm->f&BTF_CYOK) gprintf(gops, go, " cy=%.0f", tm->cy);
- return;
- }
+ rc = 0; goto end;
- x = tm->t; normalize(&x, &u, 1000);
- gprintf(gops, go, "in %.3f %ss", x, u);
- if (tm->f&BTF_CYOK) {
- x = tm->cy; normalize(&x, &u, 1000);
- gprintf(gops, go, " (%.3f %scy)", x, u);
- }
- gprintf(gops, go, ": ");
-
- x = n/tm->t; normalize(&x, &u, scale);
- gprintf(gops, go, "%.3f %s%s/s", x, u, whats);
- x = tm->t/n; normalize(&x, &u, 1000);
- gprintf(gops, go, ", %.3f %ss/%s", x, u, what);
- if (tm->f&BTF_CYOK) {
- x = tm->cy/n; normalize(&x, &u, 1000);
- gprintf(gops, go, " (%.3f %scy/%s)", x, u, what);
- }
-}
+timer_failed:
+ /* Give a `helpful' hint if the timer didn't work. */
+ if (!getenv("MLIB_BENCH_DEBUG"))
+ tvec_notice(tv, "set `MLIB_BENCH_DEBUG=t' in the environment "
+ "for more detail");
+ rc = -1;
+ goto end;
-/*----- Benchmark environment scaffolding ---------------------------------*/
+end:
+ dstr_destroy(&d);
+ return (rc);
+}
/* --- @tvec_benchsetup@ --- *
*
struct tvec_benchctx *bc = ctx;
const struct tvec_benchenv *be = (const struct tvec_benchenv *)env;
const struct tvec_env *subenv = be->env;
- struct bench_timer *bt;
- dstr d = DSTR_INIT;
- /* Basic initialization. */
bc->be = be; bc->bst = 0; bc->subctx = 0;
-
- /* Set up the benchmarking state if it hasn't been done before. */
- if (be->bst && *be->bst)
- bc->bst = *be->bst;
- else {
- bt = bench_createtimer(0);
- if (!bt)
- { tvec_skipgroup(tv, "failed to create timer"); goto timer_failed; }
- bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt);
- *be->bst = bc->bst;
- }
-
- /* If the timer isn't calibrated yet then do that now. */
- if (!(bc->bst->f&BTF_CLB)) {
- bc->bst->tm->ops->describe(bc->bst->tm, &d);
- tvec_notice(tv, "calibrating timer `%s'...", d.buf);
- if (bench_calibrate(bc->bst))
- { tvec_skipgroup(tv, "failed to calibrate timer"); goto timer_failed; }
- } else if (!(bc->bst->f&BTF_ANY))
- { tvec_skipgroup(tv, "timer broken"); goto timer_failed; }
-
- /* Save the default target time. */
- bc->dflt_target = bc->bst->target_s;
-
- /* Initialize the subordinate environment. */
-end:
- if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz);
+ if (!tvec_benchprep(tv, be->bst, BTF_INDIRECT))
+ { bc->bst = *be->bst; bc->dflt_target = bc->bst->target_s; }
+ if (subenv && subenv->ctxsz) bc->subctx = x_alloc(tv->a, subenv->ctxsz);
if (subenv && subenv->setup) subenv->setup(tv, subenv, bc, bc->subctx);
-
- /* All done. */
- dstr_destroy(&d);
- return;
-
-timer_failed:
- if (!getenv("MLIB_BENCH_DEBUG"))
- tvec_notice(tv, "set `MLIB_BENCH_DEBUG=t' in the environment "
- "for more detail");
- goto end;
}
/* --- @tvec_benchfindvar@, @setvar@ --- *
struct tvec_benchctx *bc = ctx;
const struct tvec_benchenv *be = bc->be;
const struct tvec_env *subenv = be->env;
- const struct tvec_regdef *rd;
struct tvec_output *o = tv->output;
- struct bench_timing tm;
- struct benchrun r;
- bench_fn *loopfn;
+ struct tvec_fallbackoutput fo;
+ const struct tvec_benchoutops *bo;
+ BENCH_MEASURE_DECLS;
+ struct bench_timing t;
+ unsigned long *n;
unsigned unit;
- dstr d = DSTR_INIT;
double base;
- unsigned f = 0;
-#define f_any 1u
-
- /* Fill in the easy parts of the run context. */
- r.tv = tv; r.env = subenv; r.ctx = bc->subctx;
- r.in = tv->in; r.out = tv->out; r.fn = fn;
-
- /* Decide on the run function to select. */
- if (be->riter >= 0) {
- r.n = &TVEC_REG(tv, in, be->riter)->v.u;
- loopfn = subenv && subenv->run ?
- benchloop_inner_indirect : benchloop_inner_direct;
- } else {
- r.n = 0;
- loopfn = subenv && subenv->run ?
- benchloop_outer_indirect : benchloop_outer_direct;
- }
+ int rc;
/* Decide on the kind of unit and the base count. */
base = be->niter;
- if (be->rbuf < 0) unit = TVBU_OP;
- else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
-
- /* Construct a description of the test using the identifier registers. */
- for (rd = tv->test->regs; rd->name; rd++)
- if (rd->f&TVRF_ID) {
- if (f&f_any) dstr_puts(&d, ", ");
- else f |= f_any;
- dstr_putf(&d, "%s = ", rd->name);
- rd->ty->dump(&TVEC_REG(tv, in, rd->i)->v, rd,
- TVSF_COMPACT, &dstr_printops, &d);
- }
- DPUTZ(&d);
+ if (be->rbuf < 0) unit = BTU_OP;
+ else { unit = BTU_BYTE; base *= TVEC_REG(tv, in, be->rbuf)->v.bytes.sz; }
- /* Run the benchmark. */
- o->ops->bbench(o, d.buf, unit);
- if (bench_measure(bc->bst, &tm, base, loopfn, &r))
- o->ops->ebench(o, d.buf, unit, 0);
- else
- o->ops->ebench(o, d.buf, unit, &tm);
+ /* Find the output operations and report the start. */
+ bo = tvec_outputext(tv, &o, &fo,
+ TVEC_BENCHOUTEXT, &tvec_benchoutputfallback);
+ bo->bbench(o, 0, unit);
- dstr_destroy(&d);
+ /* Run the benchmark. */
+ if (be->riter >= 0) {
+ n = &TVEC_REG(tv, in, be->riter)->v.u;
+ if (subenv && subenv->run)
+ BENCH_MEASURE(bc->bst, rc, &t, base)
+ { *n = _bench_n; subenv->run(tv, fn, bc->subctx); }
+ else
+ BENCH_MEASURE(bc->bst, rc, &t, base)
+ { *n = _bench_n; fn(tv->in, tv->out, bc->subctx); }
+ } else {
+ if (subenv && subenv->run)
+ BENCH_MEASURE(bc->bst, rc, &t, base)
+ while (_bench_n--) subenv->run(tv, fn, bc->subctx);
+ else
+ BENCH_MEASURE(bc->bst, rc, &t, base)
+ while (_bench_n--) fn(tv->in, tv->out, bc->subctx);
+ }
-#undef f_any
+ /* Report the outcome. */
+ bo->ebench(o, 0, unit, rc ? 0 : &t);
}
/* --- @tvec_benchafter@ --- *
be = bc->be; subenv = be->env;
/* Tear down any subsidiary environment. */
- if (subenv && subenv->teardown)
- subenv->teardown(tv, bc->subctx);
+ if (subenv && subenv->teardown) subenv->teardown(tv, bc->subctx);
+ if (bc->subctx) x_free(tv->a, bc->subctx);
/* If the benchmark state was temporary, then dispose of it. */
if (bc->bst) {
if (be->bst) bc->bst->target_s = bc->dflt_target;
- else { bench_destroy(bc->bst); xfree(bc->bst); }
+ else { bench_destroy(bc->bst); x_free(tv->a, bc->bst); }
}
}