Zephyrで実行時間を計測する
はじめに
Zephyrでアプリケーションを開発しており、ベンチマークが取りたくなりました。 driverが提供されているnRF52840のRTC (Real Time Counter) を使って、プログラムの実行時間を計測してみます。
最終的には、Zephyr kernelが提供しているKernel Clock APIを使用します。
nRF52840 RTC (Real Time Counter)
nRF52840には、3つのRTCが搭載されています。
ZephyrでのnRF RTC
Kernel configでは、デフォルトでdriverが有効になっています。
# Timer Drivers # CONFIG_NRF_RTC_TIMER=y
3つあるRTC (0, 1, 2)のうち、RTC1のdriverが提供されています。 これは、Zephyr kernelがsystem clockとして利用します。 0と2を利用したい場合には、どうすれば良いのでしょうね?
nrf_rtc_timer.c
NRF RTCのdriverです。
|> zephyr/drivers/timer/nrf_rtc_timer.c
ざっとAPIを一通り見てみます。
int z_clock_driver_init(struct device *device); void z_clock_set_timeout(s32_t ticks, bool idle); u32_t z_clock_elapsed(void); u32_t _timer_cycle_get_32(void);
system clockは1つだけしかない、ということが前提(当然の前提ですが)で、APIを関数ポインタで登録せずに、kernelと静的にリンクしているっぽいですね。
zephyr/drivers/timer
下にあるdriverには、全て同じ名前の関数が実装されています。
# at zephyr/include $ grep -r _timer_cycle_get_32 * ... arch/posix/arch.h:extern u32_t _timer_cycle_get_32(void); arch/posix/arch.h:#define _arch_k_cycle_get_32() _timer_cycle_get_32() arch/arm/cortex_m/misc.h:extern u32_t _timer_cycle_get_32(void); arch/arm/cortex_m/misc.h:#define _arch_k_cycle_get_32() _timer_cycle_get_32() arch/nios2/arch.h:extern u32_t _timer_cycle_get_32(void); arch/nios2/arch.h:#define _arch_k_cycle_get_32() ...
はい、予想通りですね。kernelと結びついている部分も、下の通りです。
# at zephyr/include $ grep -r _arch_k_cycle_get_32 * ... kernel.h:#define k_cycle_get_32() _arch_k_cycle_get_32()
system clock driverインタフェース
Zephyrのsystem clock driverインタフェースについて調べます。
kernel.h
で提供されているAPIは、次の通りです。k_cycle_get_32()
の実態は、上述の通り、nrf_rtc_timer.c
の_timer_cycle_get_32()
です。
ということで、カウンタがラウンドアップしない範囲であれば、計測したい処理の前後で、k_cycle_get_32()
を実行して、その差分を計算すればよいことになります。
uptime
系のAPIはミリ秒単位なので、ミリ秒単位での計測がしたい場合に使えそうです。
上記Zephyrドキュメントでも、サンプルコード付きで解説があります。
Measuring Time with Normal Precision
s64_t time_stamp; s64_t milliseconds_spent; /* capture initial time stamp */ time_stamp = k_uptime_get(); /* do work for some (extended) period of time */ ... /* compute how long the work took (also updates the time stamp) */ milliseconds_spent = k_uptime_delta(&time_stamp);
Measuring Time with High Precision
ありがたいことに、クロックサイクルからナノ秒単位に変換するマクロも提供されています。
u32_t start_time; u32_t stop_time; u32_t cycles_spent; u32_t nanoseconds_spent; /* capture initial time stamp */ start_time = k_cycle_get_32(); /* do work for some (short) period of time */ ... /* capture final time stamp */ stop_time = k_cycle_get_32(); /* compute how long the work took (assumes no counter rollover) */ cycles_spent = stop_time - start_time; nanoseconds_spent = SYS_CLOCK_HW_CYCLES_TO_NS(cycles_spent);
SYS_CLOCK_HW_CYCLES_TO_NS
マクロ
#define SYS_CLOCK_HW_CYCLES_TO_NS(X) (u32_t)(SYS_CLOCK_HW_CYCLES_TO_NS64(X)) /* SYS_CLOCK_HW_CYCLES_TO_NS64 converts CPU clock cycles to nanoseconds */ #define SYS_CLOCK_HW_CYCLES_TO_NS64(X) \ (((u64_t)(X) * NSEC_PER_SEC) / sys_clock_hw_cycles_per_sec())
sys_clock_hw_cycles_per_sec()
は定数を返す関数です。