X-Git-Url: https://git.distorted.org.uk/~mdw/mLib/blobdiff_plain/db2bf4111cde36048ac66bbac58547d105bc7e80..67b5031ec6d160b5cae425466a34d1be3b211dd4:/test/tvec-bench.c diff --git a/test/tvec-bench.c b/test/tvec-bench.c index ab21b9c..b56fcbe 100644 --- a/test/tvec-bench.c +++ b/test/tvec-bench.c @@ -33,19 +33,34 @@ /*----- Data structures ---------------------------------------------------*/ struct benchrun { - struct tvec_state *tv; - const struct tvec_env *env; - void *ctx; - unsigned long *n; - const struct tvec_reg *in; struct tvec_reg *out; - tvec_testfn *fn; + struct tvec_state *tv; /* test vector state */ + const struct tvec_env *env; /* subordinate environment */ + void *ctx; /* subordinate env's context */ + unsigned long *n; /* iteration count address */ + const struct tvec_reg *in; struct tvec_reg *out; /* register vectors */ + tvec_testfn *fn; /* test function to run */ }; /*----- Global variables --------------------------------------------------*/ -struct bench_state *tvec_benchstate; +struct bench_state *tvec_benchstate; /* common benchmarking state */ -/*----- Benchmarking ------------------------------------------------------*/ +/*----- Utilities ---------------------------------------------------------*/ + +/* --- @normalize@ --- * + * + * 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 + * + * 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. + */ static void normalize(double *x_inout, const char **unit_out, double scale) { @@ -66,6 +81,29 @@ static void normalize(double *x_inout, const char **unit_out, double scale) *x_inout = x; *unit_out = *u; } +/*----- Benchmark environment scaffolding ---------------------------------*/ + +/* --- @tvec_benchsetup@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const struct tvec_env *env@ = environment description + * @void *pctx@ = parent context (ignored) + * @void *ctx@ = context pointer to initialize + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Initialize a benchmarking environment context. + * + * The environment description must really be a @struct + * tvec_bench@. If the @bst@ slot is null, then a temporary + * benchmark state is allocated for the current test group and + * released at the end. Otherwise, it must be the address of a + * pointer to a benchmark state: if the pointer is null, then a + * fresh state is allocated and initialized and the pointer is + * updated; otherwise, the pointer is assumed to refer to an + * existing valid benchmark state. + */ + int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, void *pctx, void *ctx) { @@ -74,8 +112,10 @@ int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, const struct tvec_env *subenv = b->env; struct bench_timer *bt; + /* Basic initialization. */ bc->b = b; bc->bst = 0; bc->subctx = 0; + /* Set up the benchmarking state if it hasn't been done before. */ if (!b->bst || !*b->bst) { bt = bench_createtimer(); if (!bt) goto fail_timer; bc->bst = xmalloc(sizeof(*bc->bst)); bench_init(bc->bst, bt); @@ -84,18 +124,41 @@ int tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env, goto fail_timer; else bc->bst = *b->bst; + + /* Set the default target time. */ bc->dflt_target = bc->bst->target_s; + /* Initialize the subordinate environment. */ if (subenv && subenv->ctxsz) bc->subctx = xmalloc(subenv->ctxsz); if (subenv && subenv->setup && subenv->setup(tv, subenv, bc, bc->subctx)) { xfree(bc->subctx); bc->subctx = 0; return (-1); } + /* All done. */ end: return (0); fail_timer: tvec_skipgroup(tv, "failed to create timer"); goto end; } +/* --- @tvec_benchset@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @const char *var@ = variable name to set + * @const struct tvec_env *env@ = environment description + * @void *ctx@ = context pointer + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Set a special variable. The following special variables are + * supported. + * + * * %|@target|% is the (approximate) number of seconds to run + * the benchmark. + * + * Unrecognized variables are passed to the subordinate + * environment, if there is one. + */ + int tvec_benchset(struct tvec_state *tv, const char *var, const struct tvec_env *env, void *ctx) { @@ -117,26 +180,62 @@ int tvec_benchset(struct tvec_state *tv, const char *var, return (0); } +/* --- @tvec_benchbefore@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: Zero on success, @-1@ on failure. + * + * Use: Invoke the subordinate environment's @before@ function to + * prepare for the benchmark. + */ + int tvec_benchbefore(struct tvec_state *tv, void *ctx) { struct tvec_benchctx *bc = ctx; const struct tvec_bench *b = bc->b; const struct tvec_env *subenv = b->env; + /* Just call the subsidiary environment. */ if (subenv && subenv->before) return (subenv->before(tv, bc->subctx)); else return (0); } +/* --- @tvec_benchafter@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Invoke the subordinate environment's @after@ function to + * clean up after the benchmark. + */ + void tvec_benchafter(struct tvec_state *tv, void *ctx) { struct tvec_benchctx *bc = ctx; const struct tvec_bench *b = bc->b; const struct tvec_env *subenv = b->env; + /* Restore the benchmark state's old target. */ bc->bst->target_s = bc->dflt_target; + + /* Pass the call on to the subsidiary environment. */ if (subenv && subenv->after) subenv->after(tv, bc->subctx); } +/* --- @tvec_benchteardown@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @void *ctx@ = context pointer + * + * Returns: --- + * + * Use: Tear down the benchmark environment. + */ + void tvec_benchteardown(struct tvec_state *tv, void *ctx) { struct tvec_benchctx *bc = ctx; @@ -145,38 +244,79 @@ void tvec_benchteardown(struct tvec_state *tv, void *ctx) if (!bc) return; b = bc->b; subenv = b->env; + + /* Tear down any subsidiary environment. */ if (subenv && subenv->teardown && bc->subctx) subenv->teardown(tv, bc->subctx); + + /* If the benchmark state was temporary, then dispose of it. */ if (bc->bst) { if (b->bst) bc->bst->target_s = bc->dflt_target; else { bench_destroy(bc->bst); xfree(bc->bst); } } } -static void benchloop_outer_direct(unsigned long n, void *p) +/*----- Measurement machinery ---------------------------------------------*/ + +/* --- @benchloop_...@ --- * + * + * Arguments: @unsigned long n@ = iteration count + * @void *ctx@ = benchmark running context + * + * Returns: --- + * + * Use: Run various kinds of benchmarking loops. + * + * * 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. + * + * * 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. + */ + +static void benchloop_outer_direct(unsigned long n, void *ctx) { - struct benchrun *r = p; - tvec_testfn *fn = r->fn; void *ctx = r->ctx; + 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; - while (n--) fn(in, out, ctx); + while (n--) fn(in, out, tctx); } -static void benchloop_inner_direct(unsigned long n, void *p) - { struct benchrun *r = p; *r->n = n; r->fn(r->in, r->out, r->ctx); } +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); } -static void benchloop_outer_indirect(unsigned long n, void *p) +static void benchloop_outer_indirect(unsigned long n, void *ctx) { - struct benchrun *r = p; + 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 *ctx = r->ctx; + tvec_testfn *fn = r->fn; void *tctx = r->ctx; - while (n--) run(tv, fn, ctx); + while (n--) run(tv, fn, tctx); } -static void benchloop_inner_indirect(unsigned long n, void *p) - { struct benchrun *r = p; *r->n = n; r->env->run(r->tv, r->fn, r->ctx); } +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); } + +/* --- @tvec_benchrun@ --- * + * + * Arguments: @struct tvec_state *tv@ = test vector state + * @tvec_testfn *fn@ = test function to run + * @void *ctx@ = context pointer for the test function + * + * Returns: --- + * + * Use: Measures and reports the performance of a test function. + * + * + */ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) { @@ -194,9 +334,11 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) 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 (b->riter >= 0) { r.n = &TVEC_REG(tv, in, b->riter)->v.u; loopfn = subenv && subenv->run ? @@ -207,10 +349,12 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) benchloop_outer_indirect : benchloop_outer_direct; } + /* Decide on the kind of unit and the base count. */ base = b->niter; if (b->rbuf < 0) unit = TVBU_OP; else { unit = TVBU_BYTE; base *= TVEC_REG(tv, in, b->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, ", "); @@ -220,6 +364,7 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) TVSF_COMPACT, &dstr_printops, &d); } + /* Run the benchmark. */ o->ops->bbench(o, d.buf, unit); if (bench_measure(&tm, bc->bst, base, loopfn, &r)) o->ops->ebench(o, d.buf, unit, 0); @@ -231,6 +376,22 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx) #undef f_any } +/*----- Output utilities --------------------------------------------------*/ + +/* --- @tvec_benchreport@ --- * + * + * Arguments: @const struct gprintf_ops *gops@ = print operations + * @void *go@ = print destination + * @unsigned unit@ = the unit being measured (~TVBU_...@) + * @const struct bench_timing *tm@ = the benchmark result + * + * Returns: --- + * + * Use: Formats a report about the benchmark performance. This + * function is intended to be called on by an output + * @ebench@ function. + */ + void tvec_benchreport(const struct gprintf_ops *gops, void *go, unsigned unit, const struct bench_timing *tm) {