C++ 在自旋锁的繁忙等待中花费的CPU周期

C++ 在自旋锁的繁忙等待中花费的CPU周期,c++,c++11,spinlock,C++,C++11,Spinlock,到目前为止,我有一个很好的自旋锁,可以按预期工作: std::atomic_flag barrier = ATOMIC_FLAG_INIT; inline void lock( ){ while( barrier .test_and_set( std::memory_order_acquire ) ) {} } 但是,我想知道(指示性地)在它里面花费了多少CPU周期(如果忙等待太长,我可能会考虑一

到目前为止,我有一个很好的自旋锁,可以按预期工作:

    std::atomic_flag barrier = ATOMIC_FLAG_INIT;

    inline void lock( ){
        while( barrier
            .test_and_set( std::memory_order_acquire ) )
                {}
    }

但是,我想知道(指示性地)在它里面花费了多少CPU周期(如果忙等待太长,我可能会考虑一个互斥体,它至少会让等待线程睡眠):

当然,这并没有对锁指令本身进行计数,因此,我应该通过哪个常数来增加在忙等待中得到的周期的精确概念(我认为指令不会因为内存障碍而被流水线化,因此计数在理论上是非常精确的)? 至少在功能上是这样的

inline void lock( int & waitCounter){
    while( barrier
        .test_and_set( std::memory_order_acquire ) )
            waitCounter+=5;
    waitCounter+=2;
归结为一段代码,它记录了自身使用的指令数

.L5:
    add DWORD PTR [rdi], 5
.L3:
    mov eax, edx
    xchg    al, BYTE PTR barrier[rip]

    test    al, al
    jne .L5
    add DWORD PTR [rdi], 2
    ret

这远不是一个完整的答案,但可以给出答案。

自旋锁所需的周期数取决于许多因素,包括试图同时执行自旋锁的线程数

我最近做了这个测试


简单的回答是:由于您可以直接控制的内容(应用程序代码)和无法控制的内容(总线争用),它可能会有很大的不同。最低周期数和最大周期数之间的关系可以是110到950或更大。

使用或
std::chrono
获取时钟/时间,并据此做出决定。如果您希望周期计数达到100%的精度,在x86/64上可以使用
rdtsc
。不过,在用户空间代码中没有可用的ARM等价物。您还应该在循环中添加“暂停”指令。对于x86,这将是
rep nop
。在Windows上,可通过宏
YieldProcessor
访问rep nop(或当前体系结构的等效程序)。其他平台可能会以不同的名称提供相同的功能。您意识到您在spinlock实现中几乎犯了所有可能的错误。我绝对不会用“好”来形容它。(例如,当您最终获得锁(性能最关键的部分)时,当您退出一个紧密循环时,您将成为所有预测失误分支的母亲。)@DavidSchwartz一个好建议,但没有解释为什么是好的,它不是一个好建议(也是为什么像Paul提到的那样在循环中使用收益率?)。除了锁定明显导致的管道失速之外,您如何在不导致管道失速的情况下实现这一点?(我将始终支持有用的答案,其他用户也会这样做,即使他们没有直接解决这个问题。至少我没有那么邪恶^^)我尝试了不同的实现,他们总是受到竞争条件的影响,上面的自旋锁是唯一一个真正无锁并通过我的“彼得森测试套件”的自旋锁。
inline void lock( int & waitCounter){
    while( barrier
        .test_and_set( std::memory_order_acquire ) )
            waitCounter+=5;
    waitCounter+=2;
.L5:
    add DWORD PTR [rdi], 5
.L3:
    mov eax, edx
    xchg    al, BYTE PTR barrier[rip]

    test    al, al
    jne .L5
    add DWORD PTR [rdi], 2
    ret