ARM 上的定时器

如果测量的事件超过一微妙,可以使用软件的系统定时器,在 Linux 系统上,可以通过clock_gettime系统调用访问系统定时器。在 C++ 中访问系统定时器的标准方式是使用std::chrono

1
2
3
4
5
6
7
8
9
10
11
12
#include <cstdint>
#include <chrono>

// 返回以纳秒为单位的经过时间
uint64_t timeWithChrono() {
using namespace std::chrono;
auto start = steady_clock::now();
// 运行一些代码
auto end = steady_clock::now();
uint64_t delta = duration_cast<nanoseconds>(end - start).count();
return delta;
}

如果测量的事件是纳秒级到一分钟,可以读取CNTVCT_EL0寄存器,这是一种硬件定时器,被实现为一个硬件寄存器。时间戳计数器是单调的,并且速率恒定,即它不受频率变化的影响。每个 CPU 都有自己的时间戳计数器,它就是所经过的参考周期数。可以认为是 ARM 上 rdtsc 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static inline uint64_t
arm64_cntvct(void)
{
uint64_t tsc;
asm volatile("mrs %0, cntvct_el0" : "=r" (tsc));
return tsc;
}

static inline uint64_t
arm64_cntfrq(void)
{
uint64_t freq;
asm volatile("mrs %0, cntfrq_el0" : "=r" (freq));
return freq;
}

static inline uint64_t
rdtsc(void)
{
return arm64_cntvct();
}

注:代码来源https://blog.csdn.net/z20230508/article/details/136741584

但是读 CNTVCT_EL0 的方法也不能精确到 cpu cycle,因为其频率通常比 cpu 频率低很多,譬如在天玑9400的机器(我用的是 vivo 的 X200 Pro)上,TSC 的频率仅为13MHZ,但是 CPU 频率都是 GHZ 为单位的。

我查阅资料,看到可以通过读取 PMCCNTR_EL0 寄存器来达到 cpu cycle 精度定时,但是这个寄存器默认用户态不可访问,需要内核打开,在 6.6 内核上,按照https://ilinuxkernel.com/?p=1755目前我没有打开成功。而且在手机上大小核并且频率还会动态变化,各个cpu的频率不一样会导致计数不准确,所以用这个方法需要将测试用例绑核并且固定核的频率。