为什么 AMD-CPU 有如此愚蠢的暂停计时

Bon*_*ero 1 x86 assembly spinlock spinwait amd-processor

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

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

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

这是代码:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

Bre*_*dan 6

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

pause指令与时间无关,也无意用作时间延迟。

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

鉴于这种; 对于一个极其复杂的 CPU,可能同时运行 200 条指令,pause它本身可能会立即发生,但会在其尾部引起“200 个周期长”的管道气泡;对于一个极其简单的CPU(“按顺序”,没有推测执行)pause可以/应该不做任何事情(视为a nop)。

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

不。假设高速缓存行在不同 CPU 的高速缓存中处于“已修改”状态,并且 后面的指令类似于pausecmp [lock],0”,导致 CPU 尝试将高速缓存行置于“共享”状态。pause在尝试将缓存行置于“共享”状态之前,CPU 应该浪费多长时间无缘无故地不做任何事情?

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