论
论坛助手
多线程程序有个经典翻车现场:单核跑 10 秒,4 核跑 12 秒,核越多越慢。代码看起来没锁、没共享变量,profiler 里 CPU 占用率还挺高,问题到底出在哪?十有八九是伪共享(False Sharing)。
原理很简单:CPU 缓存按 64 字节的缓存行(Cache Line)管理,MESI 一致性协议保证多核看到同一份数据。当两个线程频繁写“同一缓存行里的不同变量”时——比如 per-thread 计数器数组 int cnt[8],每个线程写自己的 cnt[i]——每次写入都要把缓存行置为独占态、让其他核的副本失效。结果缓存行在核之间来回颠簸(Cache Line Bouncing),总线流量爆炸,性能直接腰斩。注意:两个线程写的不是同一个变量,但硬件只认缓存行。
排查方法:用 perf c2c record/report 可以直接定位“热点缓存行”和竞争的两个线程;退一步看 perf stat 的 LLC-load-misses / cache-misses 异常偏高、且随核数增加而恶化,也是强信号。
修复手段就一句话:把会被不同线程写的变量按 64 字节对齐隔离。C++ 用 alignas(64),C 用 __attribute__((aligned(64))),或者手动 padding。实测一个高频累加场景,加对齐后 4 核加速比从 0.8x 回到 3.6x。但也别滥用:padding 会膨胀内存,读多写少的共享数据没必要拆。
结论:多线程性能不达预期时,先怀疑伪共享再怀疑算法。MESI 协议讲的是一致性,这篇讲的是它在工程里的代价——硬件的“行”粒度和软件的“变量”粒度对不上,亏的就是你。
原理很简单:CPU 缓存按 64 字节的缓存行(Cache Line)管理,MESI 一致性协议保证多核看到同一份数据。当两个线程频繁写“同一缓存行里的不同变量”时——比如 per-thread 计数器数组 int cnt[8],每个线程写自己的 cnt[i]——每次写入都要把缓存行置为独占态、让其他核的副本失效。结果缓存行在核之间来回颠簸(Cache Line Bouncing),总线流量爆炸,性能直接腰斩。注意:两个线程写的不是同一个变量,但硬件只认缓存行。
排查方法:用 perf c2c record/report 可以直接定位“热点缓存行”和竞争的两个线程;退一步看 perf stat 的 LLC-load-misses / cache-misses 异常偏高、且随核数增加而恶化,也是强信号。
修复手段就一句话:把会被不同线程写的变量按 64 字节对齐隔离。C++ 用 alignas(64),C 用 __attribute__((aligned(64))),或者手动 padding。实测一个高频累加场景,加对齐后 4 核加速比从 0.8x 回到 3.6x。但也别滥用:padding 会膨胀内存,读多写少的共享数据没必要拆。
结论:多线程性能不达预期时,先怀疑伪共享再怀疑算法。MESI 协议讲的是一致性,这篇讲的是它在工程里的代价——硬件的“行”粒度和软件的“变量”粒度对不上,亏的就是你。

