[性能分析与优化]伪共享问题(perf + cpp)

伪共享问题的主要来源有两件事

  1. 每次cache读入一个数据时实际上读入整个数据行
  2. 多个线程可能会读入相同的数据行

由于cache写回内存时也是按照数据行进行写入的,导致了写回内存时并不能做到真正的共享,而是需要重新同步。

解决方案有两种

  1. 将需要并行计算的数据使用alignas等方法隔离在不同的cacheline
  2. 使用thread_local进行数据隔离

Perf

由上述分析可知,伪共享主要导致的是Cache Miss,故使用相关事件

perf stat -e cache-misses ./benchmark

用例代码

原代码
#include <iostream>
#include <thread>
#include <vector>

struct SharedData {
  int x;
  int y;
};
const int epoch = 1000000000;
void incrementX(SharedData &data) {
  for (int i = 0; i < epoch; ++i) {
    data.x++;
  }
}

void incrementY(SharedData &data) {
  for (int i = 0; i < epoch; ++i) {
    data.y++;
  }
}

int main() {
  SharedData data{0, 0};

  std::thread t1(incrementX, std::ref(data));
  std::thread t2(incrementY, std::ref(data));

  t1.join();
  t2.join();

  std::cout << "x: " << data.x << ", y: " << data.y << std::endl;
  return 0;
}
threadlocal
#include <iostream>
#include <thread>
#include <vector>

struct ThreadData {
    int x;
    int y;
};

thread_local ThreadData threadData = {0, 0};

const int epoch = 1000000000;

void incrementX() {
    for (int i = 0; i < epoch; ++i) {
        threadData.x++;
    }
}

void incrementY() {
    for (int i = 0; i < epoch; ++i) {
        threadData.y++;
    }
}

int main() {
    int totalX = 0, totalY = 0;

    std::thread t1([&totalX]() {
        incrementX();
        totalX = threadData.x; // 将线程1的结果保存到主线程变量
    });

    std::thread t2([&totalY]() {
        incrementY();
        totalY = threadData.y; // 将线程2的结果保存到主线程变量
    });

    t1.join();
    t2.join();

    std::cout << "x: " << totalX << ", y: " << totalY << std::endl;
    return 0;
}
padding 
#include <iostream>
#include <thread>
#include <vector>

struct SharedData {
  int x;
  char padding[60];
  int y;
};
const int epoch = 1000000000;
void incrementX(SharedData &data) {
  for (int i = 0; i < epoch; ++i) {
    data.x++;
  }
}

void incrementY(SharedData &data) {
  for (int i = 0; i < epoch; ++i) {
    data.y++;
  }
}

int main() {
  SharedData data{0, 0};

  std::thread t1(incrementX, std::ref(data));
  std::thread t2(incrementY, std::ref(data));

  t1.join();
  t2.join();

  std::cout << "x: " << data.x << ", y: " << data.y << std::endl;
  return 0;
}

执行结果

-O0 -gbenchmarkthreadlocalpadding
cahce-misses111727010422532208243
time(s)2.531533641.5173043140.63421379

可以发现 threadlocal方案得到的cache-misses数量几乎是padding方案的两倍,通过perf查询cpu的prefecth数据可以证明是由CPU的预取机制导致的

若使用O3,则三者性能几乎无差异

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值