Zephyr provides a set of timing APIs for measuring code execution time, helping to analyze and optimize code. These timing APIs may use a different timer than the default kernel timer, and the timer used is specified by the architecture, SoC, or board configuration.
API
The timing APIs are defined in zephyr/include/zephyr/timing/timing.h
and will only be included in the build if CONFIG_TIMING_FUNCTIONS=y
. All time descriptions in the timing system are represented using timing_t
, which is a 64-bit unsigned number.
typedef uint64_t timing_t;
// Initialize the timing subsystem
void timing_init(void);
// Start timing in the timing system
void timing_start(void);
// Stop timing in the timing system
void timing_stop(void);
// Return the timing count, in cycles
static inline timing_t timing_counter_get(void);
// Calculate the timing count between start and end
static inline uint64_t timing_cycles_get(volatile timing_t *const start,
volatile timing_t *const end);
// Get the frequency of the used counter, in Hz
static inline uint64_t timing_freq_get(void);
// Convert cycles to nanoseconds
static inline uint64_t timing_cycles_to_ns(uint64_t cycles);
// Convert cycles to nanoseconds by averaging
static inline uint64_t timing_cycles_to_ns_avg(uint64_t cycles, uint32_t count);
// Get the frequency of the used counter (in MHz).
static inline uint32_t timing_freq_get_mhz(void);
Usage Example
#include <zephyr/timing/timing.h>
void gather_timing(void)
{
timing_t start_time, end_time;
uint64_t total_cycles;
uint64_t total_ns;
// Initialize and start the timing system
timing_init();
timing_start();
// Get the start time
start_time = timing_counter_get();
code_execution_to_be_measured();
// Get the end time
end_time = timing_counter_get();
// Calculate the execution time of the code
total_cycles = timing_cycles_get(&start_time, &end_time);
total_ns = timing_cycles_to_ns(total_cycles);
// Stop the timer
timing_stop();
}
Implementation
The initialization, start, and stop implementations are found in zephyr/subsys/timing/timing.c
and zephyr/include/zephyr/timing/timing.h
, and the basic form is as follows:
<ret> timing_<function>(<param list>)
{
if (has_inited) {
return;
}
#if defined(CONFIG_BOARD_HAS_TIMING_FUNCTIONS)
board_timing_<function>();
#elif defined(CONFIG_SOC_HAS_TIMING_FUNCTIONS)
soc_timing_<function>();
#else
arch_timing_<function>();
#endif
}
For example, the initialization is as follows:
void timing_init(void)
{
if (has_inited) {
return;
}
#if defined(CONFIG_BOARD_HAS_TIMING_FUNCTIONS)
board_timing_init();
#elif defined(CONFIG_SOC_HAS_TIMING_FUNCTIONS)
soc_timing_init();
#else
arch_timing_init();
#endif
has_inited = true;
}
Getting the count is done with:
static inline timing_t timing_counter_get(void)
{
#if defined(CONFIG_BOARD_HAS_TIMING_FUNCTIONS)
return board_timing_counter_get();
#elif defined(CONFIG_SOC_HAS_TIMING_FUNCTIONS)
return soc_timing_counter_get();
#else
return arch_timing_counter_get();
#endif
}
From the implementation here, we can see that the timing functions are implemented at three levels: board, soc, and arch. When CONFIG_BOARD_HAS_TIMING_FUNCTIONS=y
, a set of timing functions implemented in the board will be used:
void board_timing_init(void);
void board_timing_start(void);
void board_timing_stop(void);
timing_t board_timing_counter_get(void);
uint64_t board_timing_cycles_get(volatile timing_t *const start,
volatile timing_t *const end);
uint64_t board_timing_freq_get(void);
uint64_t board_timing_cycles_to_ns(uint64_t cycles);
uint64_t board_timing_cycles_to_ns_avg(uint64_t cycles, uint32_t count);
uint32_t board_timing_freq_get_mhz(void);
When CONFIG_SOC_HAS_TIMING_FUNCTIONS=y
, a set of timing functions implemented in the soc will be used:
void soc_timing_init(void);
void soc_timing_start(void);
void soc_timing_stop(void);
timing_t soc_timing_counter_get(void);
uint64_t soc_timing_cycles_get(volatile timing_t *const start,
volatile timing_t *const end);
uint64_t soc_timing_freq_get(void);
uint64_t soc_timing_cycles_to_ns(uint64_t cycles);
uint64_t soc_timing_cycles_to_ns_avg(uint64_t cycles, uint32_t count);
uint32_t soc_timing_freq_get_mhz(void);
The board implementation takes precedence over the soc, and only when both board and soc implementations are absent will the arch implementation be used. Zephyr allows different hardware timers to be used for different arch, soc, and board configurations, which also leads to different hardware implementations. In version 3.4.0, the following SoCs have implemented the soc-level timing functions:
./soc/arm/microchip_mec/mec1501/timing.c
./soc/arm/microchip_mec/mec172x/timing.c
./soc/arm/nordic_nrf/timing.c
The following archs have implemented arch-level timing functions:
./arch/arm/core/aarch32/cortex_m/timing.c
./arch/nios2/core/timing.c
./arch/x86/timing.c
./arch/xtensa/core/timing.c
If there are no specific level timing function implementations, the common arch timing function implementation ./arch/common/timing.c
will be used. This implementation will directly wrap functions like k_cycle_get_32
used in the Zephyr kernel’s system timer, and the details are simple, so we will not analyze them here. Please refer to the source files for more information.
References
https://docs.zephyrproject.org/3.4.0/kernel/timing_functions/index.html