当我使用非cst内存模型时,为什么我的spinlock实现性能最差?

Nat*_*mal 6 c++ performance multithreading lock-free c++11

我在下面有两个版本的自旋锁.第一个使用默认值memory_order_cst,而后者使用memory_order_acquire/memory_order_release.由于后者更放松,我希望它有更好的表现.但似乎并非如此.

class SimpleSpinLock
{
public:

    inline SimpleSpinLock(): mFlag(ATOMIC_FLAG_INIT) {}

    inline void lock()
    {   
        int backoff = 0;
        while (mFlag.test_and_set()) { DoWaitBackoff(backoff); }
    }   

    inline void unlock()
    {   
        mFlag.clear();
    }   

private:

    std::atomic_flag mFlag = ATOMIC_FLAG_INIT;
};

class SimpleSpinLock2
{
public:

    inline SimpleSpinLock2(): mFlag(ATOMIC_FLAG_INIT) {}

    inline void lock()
    {   
        int backoff = 0;
        while (mFlag.test_and_set(std::memory_order_acquire)) { DoWaitBackoff(backoff); }
    }   

    inline void unlock()
    {   
        mFlag.clear(std::memory_order_release);
    }   

private:

    std::atomic_flag mFlag = ATOMIC_FLAG_INIT;
};

const int NUM_THREADS = 8;
const int NUM_ITERS = 5000000;

const int EXPECTED_VAL = NUM_THREADS * NUM_ITERS;

int val = 0;
long j = 0;

SimpleSpinLock spinLock;

void ThreadBody()
{
    for (int i = 0; i < NUM_ITERS; ++i)
    {   
        spinLock.lock();

        ++val; 

        j = i * 3.5 + val;  

        spinLock.unlock();
    }   
}

int main()
{
    vector<thread> threads;

    for (int i = 0; i < NUM_THREADS; ++i)
    {   
        cout << "Creating thread " << i << endl; 
        threads.push_back(std::move(std::thread(ThreadBody)));
    }   

    for (thread& thr: threads)
    {   
        thr.join();
    }   

    cout << "Final value: " << val << "\t" << j << endl;
    assert(val == EXPECTED_VAL);

    return 1;  
}
Run Code Online (Sandbox Code Playgroud)

我正在使用gcc 4.8.2运行优化O3的Ubuntu 12.04上运行.

- 带有memory_order_cst的Spinlock:

Run 1:
real    0m1.588s
user    0m4.548s
sys 0m0.052s

Run 2:
real    0m1.577s
user    0m4.580s
sys 0m0.032s

Run 3:
real    0m1.560s
user    0m4.436s
sys 0m0.032s
Run Code Online (Sandbox Code Playgroud)

- 带有memory_order_acquire/release的Spinlock:

Run 1:

real    0m1.797s
user    0m4.608s
sys 0m0.100s

Run 2:

real    0m1.853s
user    0m4.692s
sys 0m0.164s

Run 3:
real    0m1.784s
user    0m4.552s
sys 0m0.124s

Run 4:
real    0m1.475s
user    0m3.596s
sys 0m0.120s
Run Code Online (Sandbox Code Playgroud)

随着更轻松的模型,我看到更多的变化.有时它会更好.通常情况更糟,是否有人对此有解释?

kec*_*kec 6

生成的解锁代码不同.CST内存模型(使用g ++ 4.9.0)生成:

    movb    %sil, spinLock(%rip)
    mfence
Run Code Online (Sandbox Code Playgroud)

解锁.获取/发布生成:

    movb    %sil, spinLock(%rip)
Run Code Online (Sandbox Code Playgroud)

锁码是一样的.其他人会说出为什么它更适合围栏,但如果我不得不猜测,我猜它会减少总线/缓存一致性争用,可能是通过减少对总线的干扰.有时更严格更有序,因此更快.

附录:根据这个,MFENCE费用约为100次.所以也许你正在减少总线争用,因为当一个线程完成循环体时,它会在尝试重新获取锁之前暂停一下,让其他线程完成.您可以尝试通过在解锁后放入一个短暂的延迟循环来做同样的事情,尽管您必须确保它没有得到优化.

附录2:它似乎确实是由于环路过快导致的总线干扰/争用引起的.我添加了一个短延迟循环,如:

    spinLock.unlock();
    for (int i = 0; i < 5; i++) {
        j = i * 3.5 + val;
    }
Run Code Online (Sandbox Code Playgroud)

现在,获取/发布执行相同的操作.