忙轮询 std::atomic - msvc 优化循环 - 为什么,以及如何防止?

Busy polling std::atomic - msvc optimizes loop away - why, and how to prevent?

我正在尝试实现一个简单的忙循环功能。

这应该保持轮询一个 std::atomic 变量最大次数 (spinCount),并且 return 如果状态确实改变了(除了 NOT_AVAILABLE 之外的任何东西)给定的尝试,否则为假:

// noinline is just to be able to inspect the resulting ASM a bit easier - in final code, this function SHOULD be inlined!
__declspec(noinline) static bool trySpinWait(std::atomic<Status>* statusPtr, const int spinCount)
{
    int iSpinCount = 0;
    while (++iSpinCount < spinCount && statusPtr->load() == Status::NOT_AVAILABLE);
    return iSpinCount == spinCount;
}

但是,MSVC 似乎只是优化了 Win64 发布模式下的循环。我对 Assembly 很不满意,但在我看来它甚至根本没有尝试读取 statusPtr 的值:

int iSpinCount = 0;
000000013F7E2040  xor         eax,eax  
    while (++iSpinCount < spinCount && statusPtr->load() == Status::NOT_AVAILABLE);
000000013F7E2042  inc         eax  
000000013F7E2044  cmp         eax,edx  
000000013F7E2046  jge         trySpinWait+12h (013F7E2052h)  
000000013F7E2048  mov         r8d,dword ptr [rcx]  
000000013F7E204B  test        r8d,r8d  
000000013F7E204E  je          trySpinWait+2h (013F7E2042h)  
    return iSpinCount == spinCount;
000000013F7E2050  cmp         eax,edx  
000000013F7E2052  sete        al  

我的印象是 std::atomic 和 std::memory_order_sequential_cst 创建了一个编译器屏障,应该可以防止这样的事情发生,但似乎并非如此(或者更确切地说,我的理解可能是错误的)。

我在这里做错了什么,或者更确切地说 - 我怎样才能最好地实现该循环而不对其进行优化,同时对整体性能的影响最小?

我知道我可以使用#pragma optimize( "", off ),但是(除了上面的例子),在我的最终代码中我非常希望将这个调用内联到一个更大的函数中性能原因。似乎这个#pragma 通常会阻止内联。

感谢任何想法!

谢谢

but doesn't look to me like it's ever even trying to read the value of statusPtr at all

它确实会在循环的每次迭代中重新加载它:

000000013F7E2048  mov         r8d,dword ptr [rcx] # rcx is statusPtr

My impression was that std::atomic with std::memory_order_sequential_cst creates a compiler barrier that should prevent something like this,

这里只需要std::memory_order_relaxed,因为线程之间只共享一个变量(更重要的是,这段代码不会改变原子变量的值)。没有重新排序的问题。

换句话说,这个函数按预期工作。

您可能喜欢使用 PAUSE 指令,请参阅 Benefitting Power and Performance Sleep Loops