diff --git a/tests/bench_frac_div/Makefile b/tests/bench_frac_div/Makefile new file mode 100644 index 0000000000000..25161e069a2a7 --- /dev/null +++ b/tests/bench_frac_div/Makefile @@ -0,0 +1,7 @@ +include ../Makefile.tests_common + +USEMODULE += frac +USEMODULE += div +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/bench_frac_div/README.md b/tests/bench_frac_div/README.md new file mode 100644 index 0000000000000..1f4174cdc591d --- /dev/null +++ b/tests/bench_frac_div/README.md @@ -0,0 +1,20 @@ +Benchmark division +================== + +This benchmark compares performance of the frac and div modules against the +compiler generated division code. +An array of TEST_NUMOF pseudorandom numbers is used as the input to the scaling +functions. +The same scale factor is applied using three different methods: traditional +division operator (z = x / y), the frac module (z = frac_scale(frac, x)), and +the div module (where applicable, e.g. z = div_u64_by_1000000(x)) +One test uses the constant 512/15625 scaling factor used by +xtimer_ticks_from_usec when using a 32768 Hz timer. A constant scaling factor +may receive extra optimization in the compiler, so the benchmark additionally +provides two more tests of a varying denominator and a varying numerator. + +Results +======= + +The output shows the execution time (in usec by default) required for scaling +TEST_NUMOF number of pseudorandom uin64_t values with a the same scaling factor. diff --git a/tests/bench_frac_div/main.c b/tests/bench_frac_div/main.c new file mode 100644 index 0000000000000..41dfc62bb2d9e --- /dev/null +++ b/tests/bench_frac_div/main.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2018 Eistec AB + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for more + * details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Division code benchmark + * + * @author Joakim NohlgÄrd + * + * @} + */ + +#include +#include +#include + +#include "frac.h" +#include "div.h" +#include "periph/timer.h" + +#ifndef TEST_NUMOF +#define TEST_NUMOF 4096 +#endif + +#ifndef TIM_REF_DEV +#define TIM_REF_DEV TIMER_DEV(0) +#endif + +#ifndef TIM_REF_FREQ +#define TIM_REF_FREQ 1000000ul +#endif + +/* working area */ +static uint64_t buf[TEST_NUMOF]; + +#define ARRAY_LEN(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* Apply div_u64_by_15625div512 on all elements of buf */ +uint32_t bench_div_u64_by_15625div512(uint64_t *buf, size_t nelem) +{ + unsigned time_start = timer_read(TIM_REF_DEV); + for (unsigned k = 0; k < nelem; ++k) { + buf[k] = div_u64_by_15625div512(buf[k]); + } + unsigned time_end = timer_read(TIM_REF_DEV); + return (time_end - time_start); +} + +/* Use frac_scale on all elements of buf */ +uint32_t bench_frac(uint64_t *buf, size_t nelem, uint32_t num, uint32_t den) +{ + frac_t frac; + frac_init(&frac, num, den); + unsigned time_start = timer_read(TIM_REF_DEV); + for (unsigned k = 0; k < nelem; ++k) { + buf[k] = frac_scale(&frac, buf[k]); + } + unsigned time_end = timer_read(TIM_REF_DEV); + return (time_end - time_start); +} + +/* Use div_u64_by_1000000 to compute a fractional scale on all elements of buf */ +uint32_t bench_div_u64_by_1000000(uint64_t *buf, size_t nelem, uint32_t num) +{ + unsigned time_start = timer_read(TIM_REF_DEV); + for (unsigned k = 0; k < nelem; ++k) { + uint64_t x = buf[k]; + uint64_t q = div_u64_by_1000000(x); + uint32_t r = x - q * num; + buf[k] = q + div_u64_by_1000000((uint64_t)r * num); + } + unsigned time_end = timer_read(TIM_REF_DEV); + return (time_end - time_start); +} + +/* Use 64 bit division operator on all elements of buf */ +uint32_t bench_divide(uint64_t *buf, size_t nelem, uint32_t num, uint32_t den) +{ + unsigned time_start = timer_read(TIM_REF_DEV); + for (unsigned k = 0; k < nelem; ++k) { + uint64_t x = buf[k]; + uint64_t q = x / den; + uint32_t r = x - q * num; + buf[k] = q + ((uint64_t)r * num) / den; + } + unsigned time_end = timer_read(TIM_REF_DEV); + return (time_end - time_start); +} + +void timer_cb(void *arg, int chan) +{ + (void) arg; + (void) chan; + puts("Warning! spurious timer interrupt"); +} + +void fill_buf(uint64_t *buf, size_t nelem, uint64_t seed) +{ + for (unsigned k = 0; k < nelem; ++k) { + seed = 6364136223846793005ull * seed + 1; + buf[k] = seed; + } +} + +int main(void) +{ + puts("Division benchmark"); + + puts("Init timer"); + printf("TIM_REF_DEV: %u\n", (unsigned)TIM_REF_DEV); + printf("TIM_REF_FREQ: %lu\n", (unsigned long)TIM_REF_FREQ); + int res = timer_init(TIM_REF_DEV, TIM_REF_FREQ, timer_cb, NULL); + if (res < 0) { + puts("Error initializing timer!"); + while(1) {} + } + uint64_t seed = 12345; + uint32_t variation = 4321; + while (1) { + ++seed; + fill_buf(buf, ARRAY_LEN(buf), seed); + uint32_t time_div = bench_div_u64_by_15625div512(buf, ARRAY_LEN(buf)); + fill_buf(buf, ARRAY_LEN(buf), seed); + uint32_t time_frac = bench_frac(buf, ARRAY_LEN(buf), 512, 15625); + fill_buf(buf, ARRAY_LEN(buf), seed); + uint32_t time_divide = bench_divide(buf, ARRAY_LEN(buf), 512, 15625); + printf("const ( 512 / 15625) /,%%: %8" PRIu32 " frac: %8" PRIu32 " div: %8" PRIu32 "\n", time_divide, time_frac, time_div); + uint32_t var = variation % 10000ul + 995000ul; + fill_buf(buf, ARRAY_LEN(buf), seed); + time_div = bench_div_u64_by_1000000(buf, ARRAY_LEN(buf), var); + fill_buf(buf, ARRAY_LEN(buf), seed); + time_frac = bench_frac(buf, ARRAY_LEN(buf), var, 1000000ul); + fill_buf(buf, ARRAY_LEN(buf), seed); + time_divide = bench_divide(buf, ARRAY_LEN(buf), var, 1000000ul); + printf("var (%7" PRIu32 " / 1000000) /,%%: %8" PRIu32 " frac: %8" PRIu32 " div: %8" PRIu32 "\n", var, time_divide, time_frac, time_div); + fill_buf(buf, ARRAY_LEN(buf), seed); + time_frac = bench_frac(buf, ARRAY_LEN(buf), 1000000ul, var); + fill_buf(buf, ARRAY_LEN(buf), seed); + time_divide = bench_divide(buf, ARRAY_LEN(buf), 1000000ul, var); + printf("var (1000000 / %7" PRIu32 ") /,%%: %8" PRIu32 " frac: %8" PRIu32 " div: [no implementation]\n", var, time_divide, time_frac); + ++variation; + } + return 0; +}