为啥 AMD-CPU 有这么愚蠢的暂停时间

Posted

技术标签:

【中文标题】为啥 AMD-CPU 有这么愚蠢的暂停时间【英文标题】:Why have AMD-CPUs such a silly PAUSE-timing为什么 AMD-CPU 有这么愚蠢的暂停时间 【发布时间】:2021-11-18 17:33:42 【问题描述】:

我已经为 C++ 开发了一个类似于 Java 的监视器对象,并进行了一些改进。主要的改进是不仅有一个用于锁定和解锁的自旋循环,还有一个用于等待事件的循环。在这种情况下,您不必锁定互斥体,而是在 wait_poll-function 上提供谓词,并且代码反复尝试锁定互斥体轮询,如果它可以锁定互斥体,则调用返回(或移动)对的谓词布尔型和结果类型。

在内核中等待信号量和/或事件对象 (Win32) 很容易花费 1.000 到 10.000 个时钟周期,即使调用立即返回,因为之前已设置信号量或事件。所以必须有一个与这个等待间隔有合理关系的旋转计数,f.e.旋转内核中花费的最小间隔的十分之一。

使用我的监视器对象,我从 glibc 中获取了自旋计数重新计算算法。而且我也在使用暂停指令。但我发现在我的 CPU(TR 3900X)上,暂停指令太快了。平均约为 0.78ns。在 Intel-CPU 上,大约 30ns 更合理。

这是代码:

#include <iostream>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <immintrin.h>

using namespace std;
using namespace chrono;

int main( int argc, char **argv )

    static uint64_t const PAUSE_ROUNDS = 1'000'000'000;
    auto start = high_resolution_clock::now();
    for( uint64_t i = PAUSE_ROUNDS; i; --i )
        _mm_pause();
    double ns = (int64_t)duration_cast<nanoseconds>( high_resolution_clock::now() - start ).count() / (double)PAUSE_ROUNDS;
    cout << ns << endl;

为什么 AMD 采取了如此愚蠢的暂停时间? PAUSE 用于自旋等待循环,并且应该与缓存行内容翻转到不同核心并返回所需的时间紧密匹配。

【问题讨论】:

英特尔 CPU 上的暂停时间因代次而异(差异超过一个数量级),我们不能只说它是 30ns Skylake之前的Intel只有5个时钟周期左右;在他们的实验发现可以提高吞吐量后,他们在 SKL 中将其提高到 100。 (特别是考虑与另一个超线程的竞争。) 应该与高速缓存行内容翻转到不同核心并返回所需的时间紧密匹配。 - 您可能想要节省一些功率(并避免 memory_order 机器核弹) )而不会等待太长时间,平均该间隔的一半(如果您假设丢失缓存行的时间是随机的,当然这可能不是高争用)。所以更短的停顿是有道理的。不过,没这么短;我同意只有几个时钟周期似乎有点愚蠢。 我猜你必须要求 AMD 解释他们所做的任何决定。 SO 无法为他们回答这个问题。 【参考方案1】:

但我发现在我的 CPU (TR 3900X) 上,暂停指令太快了。平均约为 0.78ns。在 Intel-CPU 上,大约 30ns 更合理。

pause 指令与时间没有任何关系,也不打算用作时间延迟。

pause 的作用是防止 CPU 浪费其资源(推测性地)并行执行循环的多次迭代;这在内核中的不同逻辑处理器可以使用这些资源的超线程情况下特别有用,但对于改善条件更改时退出循环所需的时间也很有用(因为您没有“N次迭代”在条件改变之前排队的指令数)。

鉴于此;对于可能同时运行 200 条指令的极其复杂的 CPU,pause 本身可能会立即发生,但会导致“200 个周期长”的管道气泡;对于一个极其简单的 CPU(“按顺序”,没有推测性执行)pause 可能/应该什么都不做(被视为nop)。

PAUSE 用于自旋等待循环,应该与高速缓存行内容翻转到不同核心并返回所需的时间非常匹配。

没有。假设缓存行在不同 CPU 的缓存中处于“修改”状态,并且pause 之后的指令类似于“cmp [lock],0”,这会导致 CPU 尝试将缓存行置于“共享”状态。在pause 之后,但在尝试将缓存行置于“共享”状态之前,CPU 应该无缘无故地浪费时间多长时间?

注意:如果您确实需要一点时间延迟,那么您需要查看umwait 指令。不过,您不需要时间延迟-您想要超时(例如,“与pause 一起旋转;直到rdtsc 表示已经过去了一定的时间)。为此,我很想将其分解为执行“pause 并检查条件 N 次”的内部循环,然后执行“如果时间限制尚未到期,重试内部循环”的外部循环。

【讨论】:

如果您有umwait,则可以使用包括tpause 在内的其他新的waitpkg 指令(暂停到给定的TSC 截止日期,因此您可以通过运行rdtsc 并添加来设置它)。如果您想在该截止日期内每隔一段时间检查旋转条件,您可以在循环中添加增量。 (尽管如果调度意味着您在 TSC 超过您的最后期限后启动 tpause,则等待时间将是数十年(?)直到 64 位 TSC 回滚,仅受操作系统对 MSR IA32_UMWAIT_CONTROL[31:2] 的设置限制。 )

以上是关于为啥 AMD-CPU 有这么愚蠢的暂停时间的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的时间 UILabel 从暂停状态恢复播放时会跳过 2 秒?

很抱歉问这么一个愚蠢的人,但这是啥? [复制]

为啥 Chrome 会在 jQuery 中的某行暂停?

为啥JS中没有显示暂停图标

为啥 Sequelize 在 3120 条记录后会暂停?

为啥 C# 可以在运行时处理这种明显愚蠢的对象提升?