@@@ fltfmt mess
[mLib] / test / bench.h
index 2484584..3756247 100644 (file)
 
 /*----- Header files ------------------------------------------------------*/
 
+#include <limits.h>
 #include <time.h>
 
 #ifndef MLIB_BITS_H
 #  include "bits.h"
 #endif
 
+#ifndef MLIB_CONTROL_H
+#  include "control.h"
+#endif
+
 #ifndef MLIB_DSTR_H
 #  include "dstr.h"
 #endif
 
+#ifndef MLIB_GPRINTF_H
+#  include "gprintf.h"
+#endif
+
 /*----- Data structures ---------------------------------------------------*/
 
 struct bench_time {
@@ -70,6 +79,12 @@ struct bench_timerops {
   void (*describe)(struct bench_timer */*bt*/, dstr */*d*/);
     /* Write a description of the timer to @d@. */
 
+  int (*preflight)(struct bench_timer */*bt*/);
+    /* Return zero if timer is ready to go, or %$-1$% otherwise.  The @now@
+     * function will only be called following a successful @preflight@ on the
+     * same thread.
+     */
+
   int (*now)(struct bench_timer */*bt*/, struct bench_time */*t_out*/,
              unsigned /*f*/);
 #define BTF_T0 0u                      /* fetching first time of a pair */
@@ -101,6 +116,175 @@ struct bench_state {
 typedef void bench_fn(unsigned long /*n*/, void */*ctx*/);
   /* Run the benchmark @n@ times, given a context pointer @ctx@. */
 
+enum {
+  BTU_OP,                             /* counting operations of some kind */
+  BTU_BYTE,                            /* counting bytes (@rbuf >= 0@) */
+  BTU_LIMIT                            /* (number of units) */
+};
+
+/*----- Macros ------------------------------------------------------------*/
+
+/* --- @BENCH_TIMELOOP_DECLS@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Use:                Expands to the declarations needed by @BENCH_TIMELOOP_TAG@.
+ *             These should be at block scope, not at toplevel.
+ */
+
+#define BENCH_TIMELOOP_DECLS                                           \
+  struct bench_timer *_bench_tm;                                       \
+  struct bench_time _bench_t0, _bench_t1;                              \
+  unsigned long _bench_n, _bench_n1
+
+/* --- @BENCH_TIMELOOP_TAG@ --- *
+ *
+ * Arguments:  @tag@ = control structure tag
+ *             @struct bench_state *b@ = benchmark state
+ *             @struct bench_timing *delta_out@ = where to put the result
+ *             @double n@ = number of iterations
+ *
+ * Returns:    ---
+ *
+ * Use:                @BENCH_TIMELOOP_TAG(tag, b, delta_out, n) stmt@ runs @stmt@
+ *             @n@ times, capturing the time and/or CPU cycles taken in
+ *             @*delta_out@.  The iteration count must be no more than the
+ *             %%\emph{square}%% of @ULONG_MAX@.  If @stmt@ executes a free
+ *             @break@ statement, then the containing loop -- which ust
+ *             exist -- is exited.
+ *
+ *             This macro is not intended to be used directly by users: it
+ *             is used internally by @bench_calibrate@ and @BENCH_MEASURE@.
+ */
+
+#define BENCH_TIMELOOP_TAG(tag, b, delta_out, n)                       \
+  MC_BEFORE(tag##__benchtl_setup, {                                    \
+    double _R = ULONG_MAX; double _n = (n);                            \
+                                                                       \
+    _bench_tm = (b)->tm;                                               \
+    if (_n <= _R) { _bench_n1 = 0; _bench_n = _n; }                    \
+    else { _bench_n1 = _n/_R; _bench_n = _n - _R*_bench_n1; }          \
+  })                                                                   \
+  MC_TARGET(tag##__benchtl_break, { break; })                          \
+  MC_AFTER(tag##__benchtl_end, {                                       \
+    _bench_tm->ops->diff(_bench_tm, (delta_out), &_bench_t0, &_bench_t1); \
+  })                                                                   \
+  MC_DOWHILE(tag##__benchtl_countdown, _bench_n1--)                    \
+    MC_AFTER(tag##__benchtl_post, { _bench_n = ULONG_MAX; })           \
+    MC_DOWHILE(tag##__benchtl_t1,                                      \
+              _bench_tm->ops->now(_bench_tm, &_bench_t1, BTF_T1))      \
+      MC_WRAP(tag##__benchtl_t0,                                       \
+       { while (_bench_tm->ops->now(_bench_tm, &_bench_t0, BTF_T0)) ; }, \
+       { ; },                                                          \
+       { MC_GOTARGET(tag##__benchtl_break); })
+
+/* --- @BENCH_MEASURE_DECLS@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Use:                Expands to the declarations needed by @BENCH_MEASURE@.
+ *             These should be at block scope, not at toplevel.
+ */
+
+#define BENCH_MEASURE_DECLS                                            \
+  struct bench_state *_bench_b;                                                \
+  struct bench_timing *_bench_t;                                       \
+  double _bench_nn;                                                    \
+  BENCH_TIMELOOP_DECLS
+
+/* --- @BENCH_MEASURE@, @BENCH_MEASURE_TAG@ --- *
+ *
+ * Arguments:  @tag@ = control structure tag
+ *             @struct bench_state *b@ = benchmark state
+ *             @int &rc@ = where to put the result code (zero on success,
+ *                     %$-1$% on failure)
+ *             @struct bench_timing *t_out@ = where to put the result
+ *             @double base@ = number of operations per external iteration
+ *
+ * Returns:    ---
+ *
+ * Use:                @BENCH_MEASURE(b, rc, delta_out, n) stmt@ measures the
+ *             execution of @stmt@.
+ *
+ *             The statement should perform @_bench_n@ iterations of some
+ *             operation.  It will be invoked multiple times, with varying
+ *             iteration counts, so as to run for approximately
+ *             @b->target_s@ seconds.
+ *
+ *             On success, the resulting timing is left in @*t_out@, with
+ *             @t_out->n@ holding the product of the final iteration count
+ *             and @base@, and @rc@ is set to zero.  If the timer fails, or
+ *             if @stmt@ invokes a free @break@ statement, then @rc@ is set
+ *             to %$-1$%.
+ *
+ *             The macro @BENCH_MEASURE_TAG@ is the same, except that it
+ *             allows an explicit control-structure tag to be set so that it
+ *             can be used within larger control structure macros.
+ */
+
+#define BENCH_MEASURE_TAG(tag, b, rc, t_out, base)                     \
+  MC_BEFORE(tag##__benchmsr_setup, {                                   \
+    _bench_b = (b); _bench_t = (t_out); _bench_nn = 1.0;               \
+    if (bench_preflight(_bench_b)) MC_GOTARGET(tag##__benchmsr_fail);  \
+  })                                                                   \
+  MC_TARGET(tag##__benchmsr_done,                                      \
+    { bench_adjust(_bench_b, _bench_t, _bench_nn, (base)); (rc) = 0; }) \
+  MC_TARGET(tag##__benchmsr_fail, { (rc) = -1; })                      \
+  for (;;)                                                             \
+    MC_WRAP(tag##__benchmsr_loop,                                      \
+      { ; },                                                           \
+      { _bench_t->f &= _bench_b->f;                                    \
+       if (!(_bench_t->f&BTF_TIMEOK)) MC_GOTARGET(tag##__benchmsr_fail); \
+       if (bench_adapt(_bench_b, &_bench_nn, _bench_t))                \
+         MC_GOTARGET(tag##__benchmsr_done);                            \
+      },                                                               \
+      { MC_GOTARGET(tag##__benchmsr_fail); })                          \
+    BENCH_TIMELOOP_TAG(tag##__benchmsr_time, _bench_b, _bench_t, _bench_nn)
+
+#define BENCH_MEASURE(b, rc, t_out, base)                              \
+  BENCH_MEASURE_TAG(bench_measure, b, rc, t_out, base)
+
+/* --- @BENCHMARK_DECLS@ --- *
+ *
+ * Arguments:  ---
+ *
+ * Use:                Expands to the declarations needed by @BENCHMARK_TAG@.
+ *             These should be at block scope, not at toplevel.
+ */
+
+#define BENCHMARK_DECLS                                                        \
+  struct bench_timing _bench_tm;                                       \
+  int _bench_rc;                                                       \
+  BENCH_MEASURE_DECLS
+
+/* --- @BENCHMARK_TAG@ --- *
+ *
+ * Arguments:  @tag@ = control structure tag
+ *             @struct bench_state *b@ = benchmark state
+ *             @struct gprintf_ops *gops, void *go@ = output formatter
+ *             @unsigned unit@ = unit being measured (@BTU_...@ code)
+ *             @double base@ = number of units per external iteration
+ *
+ * Returns:    ---
+ *
+ * Use:                @BENCHMARK_TAG(tag, b, gops, go, unit, base) stmt@ measures
+ *             the execution of @stmt@ and writes a report to an output
+ *             formatter.  The @stmt@ should run @_bench_n@ iterations of
+ *             the operation to be measured.
+ *
+ *             No tagless version of this macro is provided, because it is
+ *             expected to be useful primarily in the construction of
+ *             higher-level macros.
+ */
+
+#define BENCHMARK_TAG(tag, b, gops, go, unit, base)                    \
+  MC_AFTER(tag##__benchmark_after, {                                   \
+    if (_bench_rc) gprintf((gops), (go), "FAILED");                    \
+    else bench_report((gops), (go), _bench_b, (unit), &_bench_tm);     \
+  })                                                                   \
+  BENCH_MEASURE_TAG(tag##__benchmark_measure,                          \
+                   (b), _bench_rc, &_bench_tm, (base))
+
 /*----- Functions provided ------------------------------------------------*/
 
 /* --- @bench_createtimer@ --- *
@@ -156,14 +340,100 @@ extern void bench_destroy(struct bench_state */*b*/);
 /* --- @bench_calibrate@ --- *
  *
  * Arguments:  @struct bench_state *b@ = bench state
+ *             @unsigned f@ = calibration flags
  *
  * Returns:    Zero on success, %$-1$% if calibration failed.
  *
  * Use:                Calibrate the benchmark state, so that it can be used to
  *             measure performance reasonably accurately.
+ *
+ *             Calibration will take into account how the subject code is
+ *             going to be located.  If you're going to use @BENCH_MEASURE@
+ *             to measure a piece of literal code, then leave @f@ zero.  If
+ *             the code to be measured is going to be executed via an
+ *             indirect branch, e.g., through the @measure@ function, then
+ *             set @BTF_INDIRECT@.
  */
 
-extern int bench_calibrate(struct bench_state */*b*/);
+#define BTF_INDIRECT 1u
+extern int bench_calibrate(struct bench_state */*b*/, unsigned /*f*/);
+
+/* --- @bench_preflight@ --- *
+ *
+ * Arguments:  @struct bench_state *b@ = benchmark state
+ *
+ * Returns:    Zero on success, %$-1$% on failure.
+ *
+ * Use:                Prepares for benchmarking on the current thread.  Current
+ *             checks are that the timer is calibrated and that it can
+ *             successfully measure time; the timer preflight is also run.
+ *
+ *             Users are unlikely to find this function useful: it's called
+ *             automatically by the @BENCH_MEASURE@ macro and the
+ *             @bench_measure@ function.
+ */
+
+extern int bench_preflight(struct bench_state */*b*/);
+
+/* --- @bench_adapt@ --- *
+ *
+ * Arguments:  @struct bench_state *b@ = benchmark state
+ *             @double *n_inout@ = number of iterations, updated
+ *             @const struct bench_timing *t@ = timing from the previous run
+ *
+ * Returns:    Nonzero if the measurement is sufficient; zero to run again.
+ *
+ * Use:                This function determines a suitable number of iterations of a
+ *             benchmark function to perform next.  It is used in a loop
+ *             such as the following.
+ *
+ *                     @double n = 1.0;@
+ *                     @struct bench_timing t;@
+ *
+ *                     @do {@
+ *                       (run @n@ iterations; set @t@ to the timing)
+ *                     @} while (!bench_adapt(b, &n, &t));@
+ *
+ *             On entry, @*n_inout@ should be the number of iterations
+ *             performed by the previous pass, and @*t@ the resulting time;
+ *             the @BTF_TIMEOK@ flag must be set @t->f@.  If the timing is
+ *             sufficient -- @t->t@ is sufficiently close to @b->target_s@
+ *             -- then the function returns nonzero to indicate that
+ *             measurement is complete.  Otherwise, it sets @*n_inout@ to a
+ *             new, larger iteration count and returns zero to indicate that
+ *             a further pass is necessary.
+ *
+ *             Users are unlikely to find this function useful: it's called
+ *             automatically by the @BENCH_MEASURE@ macro and the
+ *             @bench_measure@ function.
+ */
+
+extern int bench_adapt(struct bench_state */*b*/, double */*n_inout*/,
+                      const struct bench_timing */*t*/);
+
+/* --- @bench_adjust@ --- *
+ *
+ * Arguments:  @struct bench_state *b@ = benchmark state
+ *             @struct bench_timing *t_inout@ = timing to adjust
+ *             @double n@ = number of external iterations performed
+ *             @double base@ = number of internal operations per external
+ *                     iteration
+ *
+ * Returns:    ---
+ *
+ * Use:                Adjusts a raw timing, as captured by @BENCH_TIMELOOP@,
+ *             according to the calibration data captured in @b@.
+ *             On exit, the timing data is updated, and @t->n@ is set to the
+ *             product @n*base@.
+ *
+ *             Users are unlikely to find this function useful: it's called
+ *             automatically by the @BENCH_MEASURE@ macro and the
+ *             @bench_measure@ function.
+ */
+
+extern void bench_adjust(struct bench_state */*b*/,
+                        struct bench_timing */*t_inout*/,
+                        double /*n*/, double /*base*/);
 
 /* --- @bench_measure@ --- *
  *
@@ -188,6 +458,27 @@ extern int bench_measure(struct bench_state */*b*/,
                         struct bench_timing */*t_out*/,
                         double /*base*/, bench_fn */*fn*/, void */*ctx*/);
 
+/*----- Reporting ---------------------------------------------------------*/
+
+/* --- @bench_report@ --- *
+ *
+ * Arguments:  @const struct gprintf_ops *gops, void *gp@ = output formatter
+ *             @unsigned unit@ = unit processed by the benchmark function
+ *             @const struct bench_timing *t@ = benchmark result
+ *
+ * Returns:    ---
+ *
+ * Use:                Format, to the output identified by @gops@ and @go@, a
+ *             human-readable report of the benchmarking result @t@.  No
+ *             newline is appended.
+ *
+ *             The output format is subject to change in later versions.
+ */
+
+extern void bench_report(const struct gprintf_ops */*gops*/, void */*go*/,
+                        unsigned /*unit*/,
+                        const struct bench_timing */*t*/);
+
 /*----- That's all, folks -------------------------------------------------*/
 
 #ifdef __cplusplus