我正在使用rdtsc
在C ++中定时多个NOP指令和单个NOP指令。但是,执行NOP所需的周期数不会与执行的NOP数量成比例地增加。我对为什么会这样感到困惑。我的CPU是Intel Core i7-5600U @ 2.60Ghz。
代码如下:
#include <stdio.h>
int main() {
unsigned long long t;
t = __rdtsc();
asm volatile("nop");
t = __rdtsc() - t;
printf("rdtsc for one NOP: %llu\n", t);
t = __rdtsc();
asm volatile("nop; nop; nop; nop; nop; nop; nop;");
t = __rdtsc() - t;
printf("rdtsc for seven NOPs: %llu\n", t);
}
我得到的值如下:
rdtsc for one NOP: 78
rdtsc for seven NOPs: 91
rdtsc for one NOP: 78
rdtsc for seven NOPs: 78
在未设置处理器关联性的情况下运行。
像$ taskset -c 0 ./nop$
这样设置处理器相似性时,结果为:
rdtsc for one NOP: 78
rdtsc for seven NOPs: 78
rdtsc for one NOP: 130
rdtsc for seven NOPs: 169
rdtsc for one NOP: 78
rdtsc for seven NOPs: 143
为什么会这样?
答案 0 :(得分:3)
这里的结果可能是测量噪声和/或频率缩放,因为在printf
刚从系统调用返回后就启动了第二个间隔的计时器。
RDTSC会计算参考周期,而不是核心时钟周期,因此您主要只是发现CPU频率。 (较低的核心时钟速度=对于运行两个rdtsc指令的相同数目的核心时钟,更多的参考周期)。您的RDTSC指令基本上是背对背的;与nop
本身解码成的uops数量(在包括您的Broadwell的普通CPU上)相比,rdtsc
的指令可以忽略不计。
此外,RDTSC可以通过乱序执行来重新排序。并不是说nop
会做CPU需要等待的任何事情。它只是从发出第二个rdtsc
的微指令开始就将前端延迟了0.25或1.75个周期。 (实际上,我不确定微代码定序器是否可以在与另一个指令的uop相同的周期内发送uops。因此可能是1或2个周期。)
我对How to get the CPU cycle count in x86_64 from C++?的回答对RDTSC的工作原理有很多了解。
您可能需要pause
指令。在Skylake和更高版本上,它空闲约100个内核时钟周期,而在较早的Intel内核上,它空闲约5个周期。 或启用PAUSE + RDTSC 。 How to calculate time for an asm delay loop on x86 linux? 显示了可能有用的延迟自旋循环,该循环在给定数量的RDTSC计数下进入睡眠状态。您需要知道参考时钟速度,才能将其与纳秒关联,但是通常在Intel CPU上的额定最大非涡轮增压时钟附近。例如在4.0GHz Skylake上为4008 MHz。
如果可用,tpause
将TSC时间戳记作为唤醒时间。 (请参阅链接)。但这只是目前的低功率Tremont。
在具有超大重排序缓冲区的现代超标量/无序x86上可靠地插入NOP永远不会起作用!现代的x86并非微控制器,您可以在其中计算嵌套延迟循环的迭代次数。如果周围的代码在前端没有瓶颈,那么OoO执行人员将隐藏通过管道提供NOP的成本。
说明没有费用,您只需累加即可。要对一条指令的成本进行建模,您需要知道其延迟,前端uop计数以及所需的后端执行端口。还有对管道的任何特殊影响,例如lfence
等待所有先前的微指令退役,然后再发布。 How many CPU cycles are needed for each assembly instruction?
请注意,如果正在运行的高速缓存未命中,或者可能是非常缓慢的ALU,则所需的〜100ns的“睡眠”时间不一定足够长以耗尽乱序执行缓冲区(ROB)。依赖链。 (在人工案例之外,后者不太可能)。因此,您可能不想做类似lfence
的事情。