@@@ fltfmt mess
[mLib] / test / tvec-bench.c
index 22bd2e3..c4e123d 100644 (file)
 /*----- Header files ------------------------------------------------------*/
 
 #include "bench.h"
+
 #include "tvec.h"
+#include "tvec-bench.h"
+#include "tvec-output.h"
+#include "tvec-types.h"
 
 /*----- Data structures ---------------------------------------------------*/
 
@@ -45,165 +49,186 @@ struct benchrun {
 
 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 = &nothing;
-
-  *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@ --- *
  *
@@ -232,49 +257,12 @@ void tvec_benchsetup(struct tvec_state *tv, const struct tvec_env *env,
   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@ --- *
@@ -363,58 +351,46 @@ void tvec_benchrun(struct tvec_state *tv, tvec_testfn *fn, void *ctx)
   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@ --- *
@@ -461,13 +437,13 @@ void tvec_benchteardown(struct tvec_state *tv, void *ctx)
   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); }
   }
 }