为什么memory_order_relaxed的性能与memory_order_seq_cst相同

Kir*_*ryl 7 c++ c++11

我创建了一个简单的测试来检查怎么std::memory_order_relaxed快于std::memory_order_seq_cst对价值atomic<int>增量.然而,两种情况的表现都相同.
我的编译器:gcc版本7.3.0(Ubuntu 7.3.0-27ubuntu1~18.04)
构建参数:g ++ -m64 -O3 main.cpp -std = c ++ 17 -lpthread
CPU:Intel(R)Core(TM)i7- 2670QM CPU @ 2.20GHz,4核,每核2线程
测试代码:

#include <vector>
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
#include <functional>

std::atomic<int> cnt = {0};

void run_test_order_relaxed()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 4; ++n) {
        v.emplace_back([]() {
            for (int n = 0; n < 30000000; ++n) {
                cnt.fetch_add(1, std::memory_order_relaxed);
            }
        });
    }
    std::cout << "rel: " << cnt.load(std::memory_order_relaxed);
    for (auto& t : v)
        t.join();
    }

void run_test_order_cst()
{
    std::vector<std::thread> v;
    for (int n = 0; n < 4; ++n) {
        v.emplace_back([]() {
            for (int n = 0; n < 30000000; ++n) {
                cnt.fetch_add(1, std::memory_order_seq_cst);
            }
        });
    }
    std::cout << "cst: " << cnt.load(std::memory_order_seq_cst);
    for (auto& t : v)
        t.join();
}

void measure_duration(const std::function<void()>& func)
{
    using namespace std::chrono;
    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    func();
    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = duration_cast<milliseconds>( t2 - t1 ).count();
    std::cout << " duration: " << duration << "ms" << std::endl;
}

int main()
{
    measure_duration(&run_test_order_relaxed);
    measure_duration(&run_test_order_cst); 
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

为什么std::memory_order_relaxed并且std::memory_order_seq_cst总能产生几乎相同的结果?
结果:
rel:2411持续时间:4440ms
cst:120000164持续时间:4443ms

rsj*_*ffe 6

无论内存顺序设置如何,您都需要在两个循环中进行原子操作.事实证明,对于在大多数情况下固有强烈排序的x86处理器,这导致每个fetch_add使用相同的asm代码:lock xadd.x86处理器上的此原子操作始终是顺序一致的,因此在指定宽松内存顺序时,此处没有优化机会.

使用宽松的内存顺序可以进一步优化周围的操作,但是您的代码不提供任何进一步的优化机会,因此发出的代码是相同的.请注意,结果可能与弱有序处理器(例如,ARM)或循环内的更多数据操作(可能提供更多重新排序机会)不同.

来自cppreference(我的斜体):

std :: memory_order指定如何围绕原子操作对常规非原子内存访问进行排序.

本文对C记忆模型/ C++程序员提供许多在此的更多细节.

作为旁注,反复运行原子基准测试或在不同的x86处理器(即使是同一制造商)上运行它们可能会导致显着不同的结果,因为线程可能不会平均分布在所有核心上,并且缓存延迟会受到影响它是本地核心,同一芯片上的另一个核心,或另一个芯片.它还受特定处理器如何处理潜在一致性冲突的影响.此外,1级,2级和3级缓存的行为与ram不同,因此数据集的总大小也具有显着的影响.请参阅评估现代架构的原子操作成本.