4

我在 C++ 中计时多个 NOP 指令和单个 NOP 指令,使用rdtsc. 但是,我没有得到执行 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

为什么会这样?

4

1 回答 1

5

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

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

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

我对如何从 C++ 获取 x86_64 中的 CPU 周期数的回答?有一堆关于 RDTSC 如何工作的背景。


您可能需要pause说明。它在 Skylake 及更高版本上闲置约 100 个核心时钟周期,或在早期英特尔内核上闲置约 5 个周期。 或旋转 PAUSE + RDTSC如何计算 x86 linux 上的 asm 延迟循环的时间?显示了一个可能有用的延迟自旋循环,它休眠给定数量的 RDTSC 计数。您需要知道参考时钟速度以将其与纳秒相关联,但它通常在英特尔 CPU 上的额定最大非涡轮时钟附近。例如 4.0GHz Skylake 上的 4008 MHz。

如果可用,tpause将 TSC 时间戳作为唤醒时间。(见链接)。但目前它只是低功率的 Tremont。


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

说明没有成本,你可以加起来。要对指令的成本进行建模,您需要知道它的延迟、前端 uop 计数以及它需要哪些后端执行端口。以及对管道的任何特殊影响,例如lfence等待所有先前的微指令退休,然后才能发出后续微指令。 每条汇编指令需要多少个 CPU 周期?

另请参阅预测现代超标量处理器上的操作延迟有哪些考虑因素以及如何手动计算它们?


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

于 2019-10-15T02:22:32.657 回答