【问题标题】:Busy polling std::atomic - msvc optimizes loop away - why, and how to prevent?忙于轮询 std::atomic - msvc 优化循环 - 为什么以及如何防止?
【发布时间】:2019-05-01 10:37:05
【问题描述】:

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

这应该保持轮询 std::atomic 变量的最大次数 (spinCount),如果在给定的尝试中状态确实发生了变化(变为 NOT_AVAILABLE 以外的任何值),则返回 true,否则返回 false:

// 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 通常会阻止内联。

欣赏任何想法!

谢谢

【问题讨论】:

    标签: c++ multithreading visual-c++ atomic stdatomic


    【解决方案1】:

    但在我看来,它甚至根本没有尝试读取 statusPtr 的值

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

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

    我的印象是 std::atomic 和 std::memory_order_sequential_cst 创建了一个编译器屏障,应该防止这样的事情发生,

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

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

    你可能喜欢使用PAUSE指令,见Benefitting Power and Performance Sleep Loops。

    【讨论】:

    • @Bogey 我怀疑这个错误在别处。
    • @MaxLanghof 在 C++ 中,您仍然需要std::atomic,以便编译器不会优化循环外的负载。在std::atomic 之前,人们为此滥用了volatile。
    • @MaxLanghof 在这种情况下您将不得不asm volatile,而且您真的不想去那里。总之,只是原子的。
    • @Bogey:#pragma optimize( "", off ) 会阻止函数内联吗?这可能很重要,因为非内联函数通常必须被视为编译时重新排序的内存屏障。 (因为未知函数可能会读取/写入可能存在全局指针的任何数据。)这可能表明 this 的调用者中的非原子变量存在数据竞争,从而可以优化某些内容。
    • 谢谢@PeterCordes。我现在找到了我的错误 - #pragma 确实停止了内联,但它只是通过使调用整体稍微慢一点来“防止”错误,这反过来又触发了我正在使用的稍微不同的轮询策略 - 和错误植根于替代后备投票策略。所以上面的代码确实很好,问题出在代码的不相关部分。感谢大家的意见!
    猜你喜欢
    • 2016-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-28
    • 2020-12-12
    • 2021-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多