/*----- 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 {
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 */
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@ --- *
/* --- @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@ --- *
*
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