Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions tests/bench_arithmetic/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
include ../Makefile.tests_common

USEMODULE += xtimer

include $(RIOTBASE)/Makefile.include
55 changes: 55 additions & 0 deletions tests/bench_arithmetic/README.md
Original file line number Diff line number Diff line change
@@ -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.
261 changes: 261 additions & 0 deletions tests/bench_arithmetic/main.c
Original file line number Diff line number Diff line change
@@ -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 <joakim.nohlgard@eistec.se>
*
* @}
*/

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

#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;
}