固定计数器不会一直计数,只有当软件启用它们时才计数。通常(内核侧)perf
执行此操作,并在启动程序之前将它们重置为零。
固定计数器(如可编程计数器)具有控制是否
它们计入用户、内核或用户+内核(即始终)。我假设Linux的perf
当没有任何东西使用它们时,内核代码将它们设置为不计数。
如果您想自己使用原始 RDPMC,则需要对计数器进行编程/启用(通过设置IA32_PERF_GLOBAL_CTRL
and IA32_FIXED_CTR_CTRL
MSRs),或者通过仍然在下面运行你的程序来让 perf 为你做这件事perf
. e.g. perf stat ./a.out
如果你使用perf stat -e instructions:u ./perf ; echo $?
,固定计数器实际上会在输入代码之前归零,因此您可以通过使用获得一致的结果rdpmc
一次。否则,例如使用默认的-e instructions
(不是:u)你不知道计数器的初始值。您可以通过获取增量来解决这个问题,在开始时读取一次计数器,然后在循环之后读取一次。
退出状态只有 8 位宽,所以这个小技巧可以避免 printf 或write()
仅适用于非常小的计数。
这也意味着构建完整的 64 位是没有意义的rdpmc
结果:输入的高 32 位不影响 a 的低 8 位sub
结果是因为进位仅从低位传播到高位。一般来说,除非您期望计数 > 2^32,否则只需使用 EAX 结果。即使原始 64 位计数器在您测量的时间间隔内回绕,您的减法结果仍然是 32 位寄存器中的正确小整数。
比你的问题还要简化。另请注意缩进操作数,以便即使助记符长度超过 3 个字母,它们也可以保持在一致的列中。
segment .text
global _start
_start:
mov ecx, 1<<30 ; fixed counter: instructions
rdpmc
mov edi, eax ; start
mov edx, 10
.loop:
dec edx
jnz .loop
rdpmc ; ecx = same counter as before
sub eax, edi ; end - start
mov edi, eax
mov eax, 231
syscall ; sys_exit_group(rdpmc). sys_exit isn't wrong, but glibc uses exit_group.
在下面运行这个perf stat ./a.out
or perf stat -e instructions:u ./a.out
,我们总是得到23
from echo $?
(instructions:u
显示30,比该程序实际运行的指令数多了1,包括syscall
)
23 条指令正好是第一条指令之后的指令数rdpmc
,但包括第二个rdpmc
.
如果我们注释掉第一个rdpmc
并在下面运行它perf stat -e instructions:u
,我们不断得到26
作为退出状态,并且29
from perf
. rdpmc
是要执行的第24条指令。 (RAX 开始时初始化为零,因为这是一个 Linux 静态可执行文件,因此动态链接器之前没有运行_start
)。我想知道是否sysret
在内核中被视为“用户”指令。
但随着第一个rdpmc
注释掉,运行在perf stat -e instructions
(不是:u)给出任意值,因为计数器的起始值不固定。所以我们只是将(某个任意起点 + 26)mod 256 作为退出状态。
但请注意,RDPMC 是not序列化指令,并且可以乱序执行。一般来说你可能需要lfence
,或者(正如 John McCalpin 在您链接的线程中建议的那样)给 ECX 一个对您关心的指令结果的错误依赖。例如and ecx, 0
/ or ecx, 1<<30
有效,因为与异或归零不同,and ecx,0
不打破依赖性。
这个程序中没有发生任何奇怪的事情,因为前端是唯一的瓶颈,所以所有指令基本上一发出就执行。另外,rdpmc
就在循环之后,因此循环退出分支的分支错误预测可能会阻止它在循环结束之前发送到 OoO 后端。
面向未来读者的 PS:在 Linux 上启用用户空间 RDPMC 的一种方法,无需任何自定义模块perf
需要记录在perf_event_open(2) http://man7.org/linux/man-pages/man2/perf_event_open.2.html:
echo 2 | sudo tee /sys/devices/cpu/rdpmc # enable RDPMC always, not just when a perf event is open