忙轮询 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。
我正在尝试实现一个简单的忙循环功能。
这应该保持轮询一个 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
withstd::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。