如何在 aarch64 程序集中使用硬件性能计数器?

How do I use hardware performance counters in aarch64 assembly?

我正在尝试为 ARM 架构生成的一些程序集计时。在这种特定情况下,目标是 aarch64-unknown-linux-gnu。我真的很想倒计时到各个周期,花几个 运行s 来获得最短时间并消除方差。

我无法直接访问 ARM 硬件,所以我正在尝试 运行 我的代码在 QEMU 下。

对于 x86/x86_64 我正在使用 rdtscrdtscp 指令来 return 循环计数。

对于 aarch64 我认为我可以使用

let clocks: u64;
asm!("mrs [=10=], pmccntr_el0" : "=r" (clocks) ::: "volatile");

但是当我运行

qemu-aarch64 -L /usr/aarch64-linux-gnu myprogram

我得到

qemu: uncaught target signal 4 (Illegal instruction) - core dumped

我认为可能需要在 pmcr_el0 寄存器中设置一些位,但即使使用

从中读取
let pmcr: u32;
asm!("mrs [=13=], pmcr_el0" : "=r" (pmcr) ::: "volatile");

给出相同的 Illegal instruction 错误。

我觉得这些是需要为我启用的特权指令 - 但我找不到关于如何使用 QEMU 执行此操作的文档。

那么有没有办法访问 QEMU 中的性能硬件?有没有办法以其他方式计算周期?我真的希望它尽可能接近 x86 代码。

您似乎忘记启用 pmuserenr 寄存器中的某些位。

另外,要使用 Performance Monitors Extension,请遵循 ARMv8 architecture reference manual 的 D6 章。

请注意,QEMU 不是代码分析和优化的正确工具。

QEMU 的首要目标是仿真速度(>40 MIPS),它提供了一些对 OS 开发可靠的可行架构配置文件。 然后 QEMU 不需要支持准确的 ARMv8 性能监视器功能,当前的实现非常抽象和最小:除了循环计数器 PMCCNTR 的模型不准确之外什么都没有,而且根本没有性能监视器事件基础结构。

你最好使用普通的物理计数器来产生时间间隔:

mrs x0, cntpct_el0

要理解为什么 QEMU 上的循环计算没有用,请注意,QEMU 是一个功能模型,它基于一些假设:

1) 所有指令一条一条依次执行,每条指令消耗的时间相等:

 1 guest instruction counter tick = 1 emulated nano second << icount_time_shift

icount_time_shift 由“-icount”命令行选项指定,默认为 3。那么 1 条模拟来宾指令是 8 模拟纳秒。

指令计数器和纳秒之间的这种严格转换是QEMU动态客户代码翻译机制的一个关键概念,它允许确定性地生成翻译块(TB):纳秒驱动的外围模型绑定到TB 执行,由指令计数器驱动。

例如,您以TB 执行10 条客户指令,然后将外设时钟提前到80 ns。外设还可以告诉 TB 执行循环在 800 ns 内没有任何访客事件,并且可以将接下来的 100 条指令作为一个 TB 执行。

2) 模拟的纳秒是一个基本的时钟单位,它在 qemu 中提供时间量,并且所有其他来宾计数器都按某个整数因子从它缩放:

例如,ARM 物理系统计数器 (CNTPCT) 硬编码频率的当前 QEMU 实现是 62 MHz。然后

scale_factor = 10^9 / (62 *10^6) = 16, (division is integer)

即QEMU 每 16 个模拟纳秒增量执行一次 CNTPCT 增量。基于该规模的 ARMv8 通用定时器 QEMU 实现。

此外,QEMU 将 PMCR 实现为具有某些整数比例的计数器。

在 QEMU 中,您可以在客户程序中手动计算指令数,将其乘以某个常数,我认为它将等于您的客户代码在运行时在 QEMU 上尝试计算的值。

而且对于硬件上的真实代码 运行,结果将毫无意义:您需要使用一种专有性能模拟器来针对具有缓存模型和管道的目标微体系结构,或者直接在硬件上进行测试。