c++ - 跨线程原子变量更新反射的延迟

标签 c++ multithreading cpu-architecture atomic microbenchmark

我有兴趣探索变量中的某些写入可以跨线程反射(reflect)的最短时间。 为此,我使用全局原子变量并定期更新它。同时,另一个线程旋转并检查更新的值。两个线程都附加到单独的隔离核心(操作系统 - ubuntu)。

//global
constexpr int total = 100;
atomic<int64_t> var;
void reader()
{
    int count = 0;
    int64_t tps[total];

    int64_t last = 0;
    while(count < total)
    {
        int64_t send_tp = var.load(std::memory_order_seq_cst);
        auto tp = high_resolution_clock::now();
        int64_t curr = duration_cast<nanoseconds>(tp.time_since_epoch()).count();    

        if (send_tp != last)
        {
            last = send_tp;
            tps[count] = curr - send_tp;
            count++;
        }
    }

    for(auto i = 0; i<total; i++)
        cout << tps[i] << endl;
}
void writer()
{
    for (int i=0; i<total; i++)
    {
        auto tp = high_resolution_clock::now();
        int64_t curr = duration_cast<nanoseconds>(tp.time_since_epoch()).count();
        var.store(curr, std::memory_order_seq_cst);

        // adding delay in writes, so that none are missed
        while(duration_cast<nanoseconds>(high_resolution_clock::now() - tp).count() < 100000000);
    }
}

使用这个程序,我得到了大约 70 纳秒的中位时间。

我还尝试衡量管理费用

void overhead() {
    int count = 0;
    int64_t tps[total];

    int64_t last = 0;
    while(count < total)
    {
        auto tp1 = high_resolution_clock::now();
        int64_t to_send = duration_cast<nanoseconds>(tp1.time_since_epoch()).count();
        var.store(to_send, std::memory_order_seq_cst);

        int64_t send_tp = var.load(std::memory_order_seq_cst);
        auto tp = high_resolution_clock::now();
        int64_t curr = duration_cast<nanoseconds>(tp.time_since_epoch()).count();    

        if (send_tp != last)
        {
            last = send_tp;
            tps[count] = curr - send_tp;
            count++;
        }
    }

    for(auto i = 0; i<total; i++)
        cout << tps[i] << endl;
}

我知道原子在单线程访问中不会有太多开销,结果中位数为 30 纳秒(我猜是由于 chrono::high_resolution_clock())。

因此得出的结论是延迟约为 40 纳秒(中值)。我尝试了不同的内存顺序,例如 memory_order_relaxedrelease-acquire,但结果非常相似。

根据我的理解,所需的同步只是从相邻核心获取 L1 缓存行,所以为什么要花费大约 40 纳秒的时间。我是否遗漏了某些内容,或者对如何改进设置有任何建议?

硬件详细信息 -

Intel(R) Core(TM) i9-9900K CPU(禁用超线程)

编译:g++ file.cpp -lpthread -O3

最佳答案

40ns 线程间延迟(包括测量开销)听起来很适合现代 x86 CPU。

是的,存储时间戳并根据读取器中的时间测量结果检查它听起来很合理。

内核之间的缓存一致性消息必须通过环形总线到达 L3 切片。当加载请求(在 L2 中丢失)到达正确的 L3 切片时,它将检测(从包含的 L3 标记)另一个线程拥有处于 MESI 独占或修改状态的行,并向该核心生成一条消息。然后该核心将进行写回(并且可能将数据直接发送到请求它的核心?)

这是在桌面 CPU 上,我们知道没有其他插槽可以窥探一致性:英特尔服务器 CPU 的内存延迟和内核间延迟明显更高。

关于c++ - 跨线程原子变量更新反射的延迟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76304040/

相关文章:

c++ - Makefile 不检查头文件中的更新

c++ - 最快的内联组装自旋锁

c++ - constexpr 递归函数是否使用 if constexpr

python - 创建守护线程

c++ - 多态对象数组

c# - 使用串行端口通信发送批量消息

ios - 线程完成时的 Objective C 设置图像

python - 在不同的机器上加载 pickle 的对象

linux - 如何使用 Intel Pin 工具生成分支列表?

mips - 大端和小端