多个 nop 指令并不总是比单个 nop 指令花费更长的时间

Multiple nop instructions do not consistently take longer than a single nop instruction

我使用 rdtsc 在 C++ 中对多个 NOP 指令和单个 NOP 指令进行计时。但是,执行 NOP 所需的周期数并没有与执行的 NOP 数量成比例地增加。我对为什么会这样感到困惑。我的 CPU 是英特尔酷睿 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

为什么会这样?

您的结果可能是测量噪声 and/or 频率缩放,因为您在 printf returns 进行系统调用后立即启动第二个间隔的计时器。

RDTSC 计算参考周期,而不是核心时钟周期,因此您主要只是发现 CPU 频率。 (较低的核心时钟速度 = 对于相同数量的核心时钟,运行 两条 rdtsc 指令的参考周期更多)。您的 RDTSC 指令基本上是背靠背的; nop 指令与 rdtsc 本身解码的 uops 数量相比可以忽略不计(在正常的 CPUs 包括你的 Broadwell)。

RDTSC 也可以通过乱序执行重新排序。并不是说 nop 做了 CPU 必须等待的任何事情;它只是将前端从发出第二个 rdtsc 的 uops 延迟了 0.25 或 1.75 个周期。 (实际上我不确定微代码定序器是否可以在与来自另一条指令的微指令相同的周期内发送微指令。所以可能是 1 或 2 个周期)。

我在 How to get the CPU cycle count in x86_64 from C++? 上的回答有很多关于 RDTSC 工作原理的背景知识。


您可能需要 pause 指令。它在 Skylake 和更高版本上空闲约 100 个内核时钟周期,或在早期的 Intel 内核上空闲约 5 个周期。 或旋转 PAUSE + RDTSCHow to calculate time for an asm delay loop on x86 linux? 显示了一个可能有用的延迟自旋循环,它会休眠给定数量的 RDTSC 计数。您需要知道参考时钟速度以将其与纳秒相关联,但它通常在 Intel CPUs 上的额定最大非涡轮时钟附近。例如4.0GHz Skylake 上的 4008 MHz。

如果可用,tpause 将 TSC 时间戳作为唤醒时间。 (参见 link)。不过目前只是低功耗的Tremont


插入 NOP 永远行不通 在具有巨大重新排序缓冲区的现代超标量/乱序 x86 上可靠!现代 x86 不是微控制器,您可以在其中计算嵌套延迟循环的迭代次数。如果周围的代码在前端没有瓶颈,OoO exec 将隐藏通过管道提供 NOP 的成本。

说明没有费用,您可以将其加起来。要对一条指令的成本进行建模,您需要知道它的延迟、前端 uop 计数以及它需要哪些后端执行端口。以及管道上的任何特殊效果,例如 lfence 等待所有先前的 uops 在以后的 uops 发出之前退出。 How many CPU cycles are needed for each assembly instruction?

另见 What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?


请注意,如果运行中存在高速缓存未命中,您想要的 "sleep" ~100ns 时间不一定足够长以耗尽乱序执行缓冲区(ROB),甚至可能非常慢的 ALU 依赖链。 (后者在人工案例之外不太可能)。所以你可能不想做像 lfence.

这样的事情