gpt4 book ai didi

c++ - 避免错误共享以提高性能

转载 作者:行者123 更新时间:2023-11-30 04:59:56 25 4
gpt4 key购买 nike

#include <iostream>
#include <future>
#include <chrono>

using namespace std;
using namespace std::chrono;

int a = 0;
int padding[16]; // avoid false sharing
int b = 0;

promise<void> p;
shared_future<void> sf = p.get_future().share();

void func(shared_future<void> sf, int &data)
{
sf.get();

auto t1 = steady_clock::now();
while (data < 1'000'000'000)
++data;
auto t2 = steady_clock::now();

cout << duration<double, ratio<1, 1>>(t2 - t1).count() << endl;
}

int main()
{
thread th1(func, sf, ref(a)), th2(func, sf, ref(b));
p.set_value();
th1.join();
th2.join();

return 0;
}

我用上面的代码来演示虚假共享对性能的影响。但令我惊讶的是,填充似乎根本不会加快程序的速度。有趣的是,如果ab都是原子变量,会有明显的提升。有什么区别?

最佳答案

相同 缓存行中的 2 个原子变量由不同线程通过读-修改-写 (RMW) 操作递增时,最好检测到虚假共享。为此,每个 CPU 都必须在增量操作期间刷新存储缓冲区并锁定缓存行,即:

  • 锁定缓存行
  • 从一级缓存读取值到寄存器
  • 增加寄存器内的值
  • 写回L1缓存
  • 解锁缓存行

单个高速缓存行在 CPU 之间不断跳动的效果是显而易见的,即使使用了完整的编译器优化也是如此。强制两个变量位于不同的缓存行(通过添加填充数据)可能会显着提高性能,因为每个 CPU 都可以完全访问自己的缓存行。锁定高速缓存行仍然是必要的,但不要浪费时间获取对高速缓存行的读写访问权。

如果两个变量都是普通整数,情况就不同了,因为递增整数涉及普通加载和存储(即不是原子 RMW 操作)。
在没有填充的情况下,缓存行在内核之间跳动的影响可能仍然很明显,但规模要小得多,因为不再涉及缓存行锁定。如果您使用完全优化进行编译,整个 while 循环可能会被单个增量替换,并且不会再有任何区别。

在我的 4 核 X86 上,我得到以下数字:

atomic int, no padding, no optimization: real 57.960s, user 114.495s

atomic int, padding, no optimization: real 10.514s, user 20.793s

atomic int, no padding, full optimization: real 55.732s, user 110.178s

atomic int, padding, full optimization: real 8.712s, user 17.214s

int, no padding, no optimization: real 2.206s, user 4.348s

int, padding, no optimization: real 1.951s, user 3.853s

int, no padding, full optimization: real 0.002s, user 0.000s

int, padding, full optimization: real 0.002s, user 0.000s

关于c++ - 避免错误共享以提高性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51011967/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com