diff --git a/tests/bench_arithmetic/Makefile b/tests/bench_arithmetic/Makefile new file mode 100644 index 000000000000..8bff23ceead2 --- /dev/null +++ b/tests/bench_arithmetic/Makefile @@ -0,0 +1,5 @@ +include ../Makefile.tests_common + +USEMODULE += xtimer + +include $(RIOTBASE)/Makefile.include diff --git a/tests/bench_arithmetic/README.md b/tests/bench_arithmetic/README.md new file mode 100644 index 000000000000..b38a634c72be --- /dev/null +++ b/tests/bench_arithmetic/README.md @@ -0,0 +1,55 @@ +Benchmark for arithmetic operations +=================================== + +This benchmark measures the performance of various basic arithmetic operations +using different data types. +An array of TEST_BLOCKSIZE pseudorandom numbers is used as the input to the test +functions and applied TEST_ITERATIONS times to get a better timing average. + +Tested operations +----------------- + +The data types tested are: + + - `uint8_t` + - `uint16_t` + - `uint32_t` + - `uint64_t` + - `int8_t` + - `int16_t` + - `int32_t` + - `int64_t` + - `float` + - `double` + +The operations tested are: + + - assignment `=` + - addition `+=` + - subtraction `-=` + - multiplication `*=` + - division `/=` + - left shift `<<=` + - right shift `>>=` + - bitwise OR `|=` + - bitwise AND `&=` + - bitwise XOR `^=` + +Test function summary +--------------------- + +For variable operands, the test function will iterate over the data array and perform + + data[k] (operator) data[k+1]; + +for the tests with runtime and compile time constant second operand, the test +function instead applies: + + data[k] (operator) constant; + + +Results +======= + +The output shows the execution time (in usec by default) required for applying +the given operator to TEST_ITERATIONS * TEST_BLOCKSIZE number of pseudorandom values. diff --git a/tests/bench_arithmetic/main.c b/tests/bench_arithmetic/main.c new file mode 100644 index 000000000000..597dd29baa0c --- /dev/null +++ b/tests/bench_arithmetic/main.c @@ -0,0 +1,261 @@ +/* + * 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 Arithmetic code benchmark + * + * @author Joakim NohlgÄrd + * + * @} + */ + +#include +#include +#include + +#include "xtimer.h" + +#ifndef TEST_BLOCKSIZE +#define TEST_BLOCKSIZE 128 +#endif +#ifndef TEST_ITERATIONS +#define TEST_ITERATIONS 128 +#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_BLOCKSIZE]; + +#define ARRAY_LEN(arr) (sizeof(arr) / sizeof((arr)[0])) + +/* template for test function with variable operands */ +#define TEST_FUNC_VARIABLE(func, T, op) \ + uint32_t test_ ## T ## _ ## func ## _var(T *buf, size_t nelem) \ + { \ + xtimer_ticks32_t time_start = xtimer_now(); \ + for (unsigned k = 0; k < (nelem - 1); ++k) { \ + buf[k] op (buf[k + 1]); \ + } \ + xtimer_ticks32_t time_end = xtimer_now(); \ + return xtimer_usec_from_ticks(xtimer_diff(time_end, time_start)); \ + } + +/* template for test function with compile-time constant right operand */ +#define TEST_FUNC_CCONST(func, T, op, constant) \ + uint32_t test_ ## T ## _ ## func ## _cconst(T *buf, size_t nelem) \ + { \ + xtimer_ticks32_t time_start = xtimer_now(); \ + for (unsigned k = 0; k < (nelem - 1); ++k) { \ + buf[k] op constant; \ + } \ + xtimer_ticks32_t time_end = xtimer_now(); \ + return xtimer_usec_from_ticks(xtimer_diff(time_end, time_start)); \ + } + +/* template for test function with run-time constant right operand */ +/* This test case leaves fewer possibilities for the compiler to optimize the + * generated code than when using a compile time constant */ +#define TEST_FUNC_RCONST(func, T, op) \ + uint32_t test_ ## T ## _ ## func ## _rconst(T *buf, size_t nelem, const T constant) \ + { \ + xtimer_ticks32_t time_start = xtimer_now(); \ + for (unsigned k = 0; k < (nelem - 1); ++k) { \ + buf[k] op constant; \ + } \ + xtimer_ticks32_t time_end = xtimer_now(); \ + return xtimer_usec_from_ticks(xtimer_diff(time_end, time_start)); \ + } + +/* Instancing all three above variants with one macro */ +#define TEST_FUNC_OP_INSTANCES(func, T, op, constant) \ + TEST_FUNC_VARIABLE(func, T, op) \ + TEST_FUNC_CCONST(func, T, op, constant) \ + TEST_FUNC_RCONST(func, T, op) + +/* Further instancing all operations with one macro */ +#define TEST_FUNC_INSTANCES(T, constant) \ + TEST_FUNC_OP_INSTANCES(set, T, =, constant) \ + TEST_FUNC_OP_INSTANCES(add, T, +=, constant) \ + TEST_FUNC_OP_INSTANCES(sub, T, -=, constant) \ + TEST_FUNC_OP_INSTANCES(mul, T, *=, constant) \ + TEST_FUNC_OP_INSTANCES(div, T, /=, constant) \ + TEST_FUNC_OP_INSTANCES(lsh, T, <<=, (sizeof(T) * 8 - 1) & (constant)) \ + TEST_FUNC_OP_INSTANCES(rsh, T, >>=, (sizeof(T) * 8 - 1) & (constant)) \ + TEST_FUNC_OP_INSTANCES( or, T, |=, constant) \ + TEST_FUNC_OP_INSTANCES(and, T, &=, constant) \ + TEST_FUNC_OP_INSTANCES(xor, T, ^=, constant) \ + +#define TEST_FUNC_FLOAT_INSTANCES(T, constant) \ + TEST_FUNC_OP_INSTANCES(set, T, =, constant) \ + TEST_FUNC_OP_INSTANCES(add, T, +=, constant) \ + TEST_FUNC_OP_INSTANCES(sub, T, -=, constant) \ + TEST_FUNC_OP_INSTANCES(mul, T, *=, constant) \ + TEST_FUNC_OP_INSTANCES(div, T, /=, constant) + +/* Run one (op,T) test */ +#define RUN_TEST(func, T, op, rconst, fill_func) \ + time_var = 0; \ + time_cconst = 0; \ + time_rconst = 0; \ + for (unsigned j = 0; j < TEST_ITERATIONS; ++j) { \ + fill_func((T *)buf, ARRAY_LEN(buf), seed); \ + time_var += test_ ## T ## _ ## func ## _var((T *)buf, ARRAY_LEN(buf)); \ + } \ + for (unsigned j = 0; j < TEST_ITERATIONS; ++j) { \ + fill_func((T *)buf, ARRAY_LEN(buf), seed); \ + time_cconst += test_ ## T ## _ ## func ## _cconst((T *)buf, ARRAY_LEN(buf)); \ + } \ + for (unsigned j = 0; j < TEST_ITERATIONS; ++j) { \ + fill_func((T *)buf, ARRAY_LEN(buf), seed); \ + time_rconst += test_ ## T ## _ ## func ## _rconst((T *)buf, ARRAY_LEN(buf), rconst); \ + } \ + printf("%8s, %3s: %7" PRIu32 " %7" PRIu32 " %7" PRIu32 "\n", #T, #op, time_var, time_cconst, time_rconst) \ + +/* Run all tests for the given type T */ +#define RUN_TESTS(T, rconst) \ + RUN_TEST(set, T, =, rconst, fill_buf_ ## T); \ + RUN_TEST(add, T, +=, rconst, fill_buf_ ## T); \ + RUN_TEST(sub, T, -=, rconst, fill_buf_ ## T); \ + RUN_TEST(mul, T, *=, rconst, fill_buf_ ## T); \ + RUN_TEST(div, T, /=, rconst, fill_buf_ ## T); \ + RUN_TEST(lsh, T, <<=, (sizeof(T) * 8 - 1) & (rconst), fill_buf_ ## T); \ + RUN_TEST(rsh, T, >>=, (sizeof(T) * 8 - 1) & (rconst), fill_buf_ ## T); \ + RUN_TEST( or, T, |=, rconst, fill_buf_ ## T); \ + RUN_TEST(and, T, &=, rconst, fill_buf_ ## T); \ + RUN_TEST(xor, T, ^=, rconst, fill_buf_ ## T); + +#define RUN_DOUBLE_TESTS(T, rconst) \ + RUN_TEST(set, T, =, rconst, fill_buf_double); \ + RUN_TEST(add, T, +=, rconst, fill_buf_double); \ + RUN_TEST(sub, T, -=, rconst, fill_buf_double); \ + RUN_TEST(mul, T, *=, rconst, fill_buf_double); \ + RUN_TEST(div, T, /=, rconst, fill_buf_double); + +#define RUN_FLOAT_TESTS(T, rconst) \ + RUN_TEST(set, T, =, rconst, fill_buf_float); \ + RUN_TEST(add, T, +=, rconst, fill_buf_float); \ + RUN_TEST(sub, T, -=, rconst, fill_buf_float); \ + RUN_TEST(mul, T, *=, rconst, fill_buf_float); \ + RUN_TEST(div, T, /=, rconst, fill_buf_float); + +#define RUN_ALL_TESTS() \ + RUN_TESTS( uint8_t, 211u) \ + RUN_TESTS(uint16_t, 64951u) \ + RUN_TESTS(uint32_t, 3392920849ul) \ + RUN_TESTS(uint64_t, 16611429676448458297ull) \ + RUN_TESTS( int8_t, 113) \ + RUN_TESTS( int16_t, 30047) \ + RUN_TESTS( int32_t, 2048015227l) \ + RUN_TESTS( int64_t, 6576191449585486549ll) \ + RUN_FLOAT_TESTS( float, 4.81047738097f) \ + RUN_DOUBLE_TESTS(double, 1.31083249443) + +TEST_FUNC_INSTANCES( uint8_t, 211u) \ +TEST_FUNC_INSTANCES(uint16_t, 64951u) \ +TEST_FUNC_INSTANCES(uint32_t, 3392920849ul) \ +TEST_FUNC_INSTANCES(uint64_t, 16611429676448458297ull) \ +TEST_FUNC_INSTANCES( int8_t, 113) \ +TEST_FUNC_INSTANCES( int16_t, 30047) \ +TEST_FUNC_INSTANCES( int32_t, 2048015227l) \ +TEST_FUNC_INSTANCES( int64_t, 6576191449585486549ll) \ +TEST_FUNC_FLOAT_INSTANCES( float, 4.81047738097f) \ +TEST_FUNC_FLOAT_INSTANCES(double, 1.31083249443) + +static uint64_t lcg64(uint64_t seed) +{ + return 6364136223846793005ull * seed + 1; +} + +/* Basic filler function template */ +#define FILLER_FUNC(T, shift) \ + void fill_buf_ ## T (T *buf, size_t nelem, uint64_t seed) \ + { \ + for (unsigned k = 0; k < nelem; ++k) { \ + do { \ + seed = lcg64(seed); \ + buf[k] = (T)(seed / (1ull << shift)); /* Upper bits have better entropy */ \ + } while (buf[k] == 0); /* to avoid division by zero later */ \ + } \ + } + +FILLER_FUNC( uint8_t, 56) +FILLER_FUNC(uint16_t, 48) +FILLER_FUNC(uint32_t, 32) +FILLER_FUNC(uint64_t, 0) + +FILLER_FUNC( int8_t, 56) +FILLER_FUNC( int16_t, 48) +FILLER_FUNC( int32_t, 32) +FILLER_FUNC( int64_t, 0) + +/* A simple floating point variation of the above filler function */ +void fill_buf_double(double *buf, size_t nelem, uint64_t seed) +{ + for (unsigned k = 0; k < nelem; ++k) { + do { + seed = 6364136223846793005ull * seed + 1; + } while (seed == 0); /* to avoid division by zero later */ + buf[k] = ((double) seed) / (1ul << 31); + } +} + +void fill_buf_float(float *buf, size_t nelem, uint64_t seed) +{ + for (unsigned k = 0; k < nelem; ++k) { + do { + seed = 6364136223846793005ull * seed + 1; + } while (seed == 0); /* to avoid division by zero later */ + buf[k] = ((float) seed) / (1ul << 31); + } +} + +void timer_cb(void *arg, int chan) +{ + (void) arg; + (void) chan; + puts("Warning! spurious timer interrupt"); +} + +int main(void) +{ + puts("Arithmetic 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; + uint32_t time_var = 0; + uint32_t time_cconst = 0; + uint32_t time_rconst = 0; + printf("\n\nTime in 1/%lu seconds for applying operator op to %u blocks of size %u (%lu values)\n\n", + TIM_REF_FREQ, TEST_ITERATIONS, TEST_BLOCKSIZE, (unsigned long) TEST_ITERATIONS * TEST_BLOCKSIZE); + printf("%8s, %3s: %7s %7s %7s\n", "type", "op", "var", "cconst", "rconst"); + RUN_ALL_TESTS(); + ++variation; + } + return 0; +}