【问题标题】:Multiple nop instructions do not consistently take longer than a single nop instruction多条 nop 指令不会始终比单条 nop 指令花费更长的时间
【发布时间】:2019-10-15 01:30:15
【问题描述】:

我正在使用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

为什么会这样?

【问题讨论】:

  • 对于 x86 以及您编写基准的方式,这不足为奇。你真正想做的是什么?
  • 我试图在十分之一微秒范围内睡觉。但是当我使用nanosleep,睡眠间隔设置为甚至一个纳秒时,nanosleep 的执行最终会花费>20000 个周期(根据rdtsc)。这就是为什么我尝试使用 nops 直接导致非常小的延迟。
  • 您可能需要pause 指令。它在 Skylake 及更高版本上闲置约 100 个周期,或在早期的 Intel 内核上闲置约 5 个周期。或旋转 RDTSC。在具有巨大重新排序缓冲区的现代超标量/无序 x86 上,插入 NOP 永远不会可靠地工作!您的“睡眠”时间甚至不一定长到足以耗尽乱序执行缓冲区(ROB)。说明没有成本,您可以加起来。 What considerations go into predicting latency for operations on modern superscalar processors and how can I calculate them by hand?.
  • @PeterCordes 在 RDTSC 上旋转工作!
  • 你不会像这样延迟工作时间代码。需要使用计时器。

标签: assembly inline-assembly processor rdtsc nop


【解决方案1】:

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

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

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 个核心时钟周期,或在早期英特尔内核上闲置约 5 个周期。 或在 PAUSE + RDTSC 上旋转。 How to calculate time for an asm delay loop on x86 linux? 显示了一个可能有用的延迟自旋循环,它会休眠给定数量的 RDTSC 计数。您需要知道参考时钟速度以将其与纳秒相关联,但它通常在英特尔 CPU 上的额定最大非涡轮时钟附近。例如4.0GHz Skylake 上的 4008 MHz。

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


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

说明没有费用,您可以加起来。要对指令的成本进行建模,您需要知道它的延迟、前端 uop 计数以及它需要哪些后端执行端口。以及对管道的任何特殊影响,例如 lfence 等待所有先前的 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?


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

【讨论】:

  • 不使用rdtscp 解决重新排序问题?
  • @GavinPortwood:部分。这是一个单向障碍,因此对于计时区域的结束很有用。
  • @Peter - 它被记录为单向障碍,但我没有发现任何证据表明它除了完全障碍外还有其他行为(当我尝试它时,它似乎具有类似 lfence 的完全障碍语义) .我想它仍然很有用,因为它与指令相关,因此您不需要两个围栏:之前和之后(以防止可能成为问题的两种不同类型的移动)。
  • @BeeOnRope:嗯,是的,正如我们所讨论的,尚不清楚为什么lfence; rdtsc; lfence 会更好,但在实践中似乎确实如此。所以我认为我们可以在真正的硬件上说rdtscp = lfence; rdtsc 几乎完全一样,假设lfence uops(几乎)是第一个。无论您是否在计时区域的底部添加另一个lfence,都是完全不同的事情。
  • 但是,是的,最旧的就绪优先 = 我的想法也没有问题,因为执行单元是完全流水线的,我不希望 rdtsc 触及分隔线。物理寄存器分配发生在问题/重命名/分配时间,但有一些取决于执行。 (顺便说一句,我不知道所有 22 个 rdtscp 微指令实际上做了什么,以及在执行所有早期微指令之前没有对时钟进行采样的情况下,它们中有多少 可以 进入一个 lfence。这似乎是一个明智的选择是先做 lfence 部分,即使它不是必须的)。
猜你喜欢
  • 2018-06-23
  • 2018-11-22
  • 1970-01-01
  • 2017-03-08
  • 1970-01-01
  • 2022-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多