diff --git a/tests/bench_xtimer/Makefile b/tests/bench_xtimer/Makefile new file mode 100644 index 000000000000..0c402e3072db --- /dev/null +++ b/tests/bench_xtimer/Makefile @@ -0,0 +1,76 @@ +include ../Makefile.tests_common + +USEMODULE += xtimer + +# this test uses 1000 timers by default. for boards that boards don't have +# enough memory, reduce that to 100 or 20, unless NUMOF_TIMERS has been overridden. +LOW_MEMORY_BOARDS += \ + airfy-beacon \ + arduino-mega2560 \ + b-l072z-lrwan1 \ + blackpill \ + blackpill-128kib \ + bluepill \ + bluepill-128kib \ + calliope-mini \ + cc1352-launchpad \ + cc2650-launchpad \ + cc2650stk \ + chronos \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + lsn50 \ + maple-mini \ + microbit \ + msb-430 \ + msb-430h \ + nrf51dongle \ + nrf6310 \ + nucleo-f030r8 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f103rb \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + nucleo-l073rz \ + opencm904 \ + saml10-xpro \ + saml11-xpro \ + spark-core \ + stm32f0discovery \ + stm32l0538-disco \ + telosb \ + waspmote-pro \ + wsn430-v1_3b \ + wsn430-v1_4 \ + yunjia-nrf51822 \ + z1 \ + # + +SUPER_LOW_MEMORY_BOARDS += \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + nucleo-f031k6 \ + stm32f030f4-demo \ + # + +ifneq (, $(filter $(BOARD), $(LOW_MEMORY_BOARDS))) + NUMOF_TIMERS ?= 100 +endif + +ifneq (, $(filter $(BOARD), $(SUPER_LOW_MEMORY_BOARDS))) + NUMOF_TIMERS ?= 20 +endif + +NUMOF_TIMERS ?= 1000 + +CFLAGS += -DNUMOF_TIMERS=$(NUMOF_TIMERS) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/bench_xtimer/README.md b/tests/bench_xtimer/README.md new file mode 100644 index 000000000000..a6a2714c6031 --- /dev/null +++ b/tests/bench_xtimer/README.md @@ -0,0 +1,77 @@ +# Introduction + +This test executes some benchmarks for xtimer's set() / remove() / now() +operations. + +# Details + +This set of benchmarks measures xtimer's list operation efficiency. +Depending on the available memory, the individual benchmarks that are using +multiple timers are run with either 1000 (the default), 100 or 20 timers. +Each benchmark is repeated REPEAT times (default 1000). +As only the operations are benchmarked, it is asserted that no timer ever +actually triggers. + +### set() one + +This repeatedly sets one timer in an otherwise emtpy list. +All but the first iteration will cause xtimer to implicitly remove the timer +first. +All iterations will cause the underlying periph timer to be updated. + +### remove() one + +This repeatedly removes one timer from the list. In all but the first iteration, +this list will be empty. + + +### set() + remove() one + +This repeatedly first sets, then removes one timer. The list is empty +before and after each iteration. +All iterations will cause the underlying periph timer to be updated. + +### set() many increasing targets + +This adds NUMOF timers with increasing target times. Each iteration will add a +timer at the end of xtimer's timer list. +Only the first iteration will cause the underlying periph timer to be updated. +After this test, the list is populated with NUMOF timers. + +### re-set() first + +This repatedly re-sets the first timer in the list (to the same target time). +All iterations will cause the underlying periph timer to be updated. + +### re-set() middle + +This repatedly re-sets the timer in the middle of the list (to the same target +time). + +### re-set() last + +This repatedly re-sets the last timer in the list (to the same target time). + +### remove() + set() first, middle, last + +Same as the previous three, but does a remove() before set(). + +### remove() many decreasing + +This removes all timers from the list, starting with the last. + +### xtimer_now() + +This simply calls xtimer_now() in a loop. + + +# How to interpret results + +The aim is to measure the time spent in xtimer's list operations. +Lower values are better. +The first/middle/last tests give an idea of the best case / average case / +worst case when running the operation with NUMOF timers. +Note that every set() on an already set timer will trigger an implicit remove(), +thus the timer list has to be iterated twice. +The tests that do a remove() before set() show whether xtimer correctly +identifies an unset timer. diff --git a/tests/bench_xtimer/main.c b/tests/bench_xtimer/main.c new file mode 100644 index 000000000000..f2777e2b8613 --- /dev/null +++ b/tests/bench_xtimer/main.c @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2019 Kaspar Schleiser + * + * 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 xtimer set / remove / now benchmark application + * + * @author Kaspar Schleiser + * + * @} + */ + +#include +#include + +#include "msg.h" +#include "thread.h" +#include "xtimer.h" + +#ifndef NUMOF_TIMERS +#define NUMOF_TIMERS (1000U) +#endif + +#ifndef REPEAT +#define REPEAT (1000U) +#endif + +#ifndef BASE +#define BASE (100000000LU) +#endif + +#ifndef SPREAD +#define SPREAD (10000LU) +#endif + +static xtimer_t _timers[NUMOF_TIMERS]; + +/* This variable is set by any timer that actually triggers. As the test is + * only testing set/remove/now operations, timers are not supposed to trigger. + * Thus, after every test there's an 'assert(!_triggers)' + */ +static unsigned _triggers; + +/* + * The test assumes that first, middle and last will always end up in at the + * same index within the timer queue. In order to compensate for the time that + * previous operations take themselves, the interval is corrected. The + * variables "start" and "_base" are used for that. + */ +uint32_t _base; + +static void _callback(void *arg) { + unsigned *triggers = arg; + *triggers += 1; +} + +/* returns the interval for timer 'n' that has to be set in order to insert it + * into position n */ +static uint32_t _timer_val(unsigned n) +{ + return _base + (SPREAD * n); +} + +/* set timer 'n' to its intended position 'n' */ +static void _timer_set(unsigned n) +{ + xtimer_set(&_timers[n], _timer_val(n)); +} + +/* remove timer 'n' */ +static void _timer_remove(unsigned n) +{ + xtimer_remove(&_timers[n]); +} + +static void _print_result(const char *desc, unsigned n, uint32_t total) +{ + printf("%30s %8"PRIu32" / %u = %"PRIu32"\n", desc, total, n, total/n); +} + +int main(void) +{ + puts("xtimer benchmark application.\n"); + + unsigned n; + uint32_t before, diff, start; + + /* initializing timer structs */ + for (unsigned int n = 0; n < NUMOF_TIMERS; n++) { + _timers[n].callback = _callback; + _timers[n].arg = &_triggers; + } + + start = xtimer_now_usec(); + + /* + * test setting one set timer REPEAT times + * + */ + _base = BASE; + before = xtimer_now_usec(); + for (n = 0; n < REPEAT; n++) { + _timer_set(0); + } + + diff = xtimer_now_usec() - before; + + _print_result("set() one", REPEAT, diff); + assert(!_triggers); + + /* + * test removing one unset timer REPEAT times + * + */ + before = xtimer_now_usec(); + for (n = 0; n < REPEAT; n++) { + _timer_remove(0); + } + + diff = xtimer_now_usec() - before; + + _print_result("remove() one", REPEAT, diff); + assert(!_triggers); + + /* + * test setting / removing one timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_set(0); + _timer_remove(0); + } + + diff = xtimer_now_usec() - before; + + _print_result("set() + remove() one", REPEAT, diff); + assert(!_triggers); + + /* + * test setting NUMOF_TIMERS timers with increasing targets + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (unsigned int n = 0; n < NUMOF_TIMERS; n++) { + _timer_set(n); + } + + diff = xtimer_now_usec() - before; + + _print_result("set() many increasing target", NUMOF_TIMERS, diff); + assert(!_triggers); + + /* + * test re-setting first timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_set(0); + } + + diff = xtimer_now_usec() - before; + + _print_result("re-set() first", REPEAT, diff); + assert(!_triggers); + + /* + * test setting middle timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_set(NUMOF_TIMERS/2); + } + + diff = xtimer_now_usec() - before; + + _print_result("re-set() middle", REPEAT, diff); + assert(!_triggers); + + /* + * test setting last timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_set(NUMOF_TIMERS - 1); + } + + diff = xtimer_now_usec() - before; + + _print_result("re-set() last", REPEAT, diff); + assert(!_triggers); + + /* + * test removing / setting first timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_remove(0); + _timer_set(0); + } + + diff = xtimer_now_usec() - before; + + _print_result("remove() + set() first", REPEAT, diff); + assert(!_triggers); + + /* + * test removing / setting middle timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_remove(NUMOF_TIMERS/2); + _timer_set(NUMOF_TIMERS/2); + } + + diff = xtimer_now_usec() - before; + + _print_result("remove() + set() middle", REPEAT, diff); + assert(!_triggers); + + /* + * test removing / setting last timer REPEAT times + * + */ + before = xtimer_now_usec(); + _base = BASE - (before - start); + for (n = 0; n < REPEAT; n++) { + _timer_remove(NUMOF_TIMERS - 1); + _timer_set(NUMOF_TIMERS - 1); + } + + diff = xtimer_now_usec() - before; + + _print_result("remove() + set() last", REPEAT, diff); + assert(!_triggers); + + /* + * test removing NUMOF_TIMERS timers (latest first) + * + */ + before = xtimer_now_usec(); + for (n = 0; n < NUMOF_TIMERS; n++) { + _timer_remove(NUMOF_TIMERS - n - 1); + } + + diff = xtimer_now_usec() - before; + + _print_result("remove() many decreasing", NUMOF_TIMERS, diff); + assert(!_triggers); + + /* + * test xtimer_now() + * + */ + before = xtimer_now_usec(); + n = REPEAT; + while (n--) { + xtimer_now_usec(); + } + + diff = xtimer_now_usec() - before; + + _print_result("xtimer_now()", REPEAT, diff); + assert(!_triggers); + + puts("done."); + + return 0; +} diff --git a/tests/bench_xtimer/tests/01-run.py b/tests/bench_xtimer/tests/01-run.py new file mode 100755 index 000000000000..c8fb15b3bede --- /dev/null +++ b/tests/bench_xtimer/tests/01-run.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2019 Freie Universität Berlin +# +# 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. + +import sys +from testrunner import run + + +def testfunc(child): + child.expect_exact("xtimer benchmark application.\r\n") + for i in range(12): + child.expect(r"\s+[\w() _\+]+\s+\d+ / \d+ = \d+\r\n") + + child.expect_exact("done.\r\n") + + +if __name__ == "__main__": + sys.exit(run(testfunc))