伪共享不是bug但会严重损害多线程性能,表现为缓存未命中激增、吞吐随核心数增加反降;可通过perf stat/cache分析、ThreadSanitizer排除竞争、alignas或thread_local优化。

伪共享不是 bug,但会让多线程性能掉进深坑——4 线程跑得比 2 线程还慢,perf stat 显示 cache-misses 翻倍,LLC-load-misses 高企,基本就是它在作祟。
怎么确认是伪共享而不是锁竞争或数据竞争
先排除干扰项:如果代码里没用 mutex、没用 std::atomic 的读-修改-写(如 fetch_add)争用,但多线程吞吐随核心数增加不升反降,就要怀疑伪共享。
- 用
ThreadSanitizer检查:它不会报任何data race,因为变量逻辑上完全独立 - 用
perf stat -e cache-misses,LLC-load-misses,instructions,cycles对比单/多线程:缓存未命中率显著上升,而指令数和周期数不成比例增长,就是典型信号 - 看热点内存地址:用
perf record -e mem-loads -j any,u --call-graph dwarf+perf script,找频繁访问但分散在不同线程的相邻地址 - 临时加
std::this_thread::yield()或std::this_thread::sleep_for(1ns):伪共享下加 yield 反而可能提速——因为给了缓存同步喘息时间,这很反直觉,但很说明问题
alignas(64) 是起点,但不是万能解
alignas(64) 能让变量起始地址对齐到 64 字节边界,但它只管开头,不管大小。两个 alignas(64) std::atomic<int64_t></int64_t> 变量若紧挨着分配,仍可能落在同一缓存行里。
- 结构体成员隔离更可靠:用
alignas(64)+char pad[64 - sizeof(std::atomic<int64_t>)]</int64_t>精确撑满一行 - 数组场景慎用:若定义
alignas(64) Counter arr[N],每个元素占满 64 字节,内存开销直接翻倍甚至更多,得权衡 - 别硬写
64:老平台或 ARM 嵌入式可能用 32 字节缓存行,优先用std::hardware_destructive_interference_size,但必须加宏保护 - 示例安全写法:
#include <new>
#if defined(__cpp_lib_hardware_interference_size) && __cpp_lib_hardware_interference_size >= 201606L
constexpr auto cache_line = std::hardware_destructive_interference_size;
#else
constexpr auto cache_line = 64;
#endif
<p>struct alignas(cache_line) PaddedCounter {
std::atomic<int64_t> value;
char pad[cache_line - sizeof(std::atomic<int64_t>)];
};thread_local 比 padding 更干净的解法
如果每个线程只读写自己的副本,根本不需要共享内存布局——thread_local 让变量在线程本地存储区分配,天然避开所有缓存行冲突。
立即学习“C++免费学习笔记(深入)”;
- 适用于计数器、累加器、状态快照等“写多读少”且无需跨线程即时同步的场景
- 比结构体填充省内存:没有 padding 浪费,也不依赖对齐控制
- 注意生命周期:静态
thread_local在线程退出时自动析构;函数内thread_local首次进入才构造 - 不要和
static混用:比如static thread_local int counter;是合法的,但语义容易混淆,建议分开声明
perf 工具定位伪共享的实操要点
光看 cache-misses 不够,要结合缓存行粒度抓真实冲突点。
- 用
perf record -e cycles,instructions,mem-loads,mem-stores -j any,u --call-graph dwarf录制运行时行为 - 导出地址热点:
perf script | awk '{print $3}' | sort | uniq -c | sort -nr | head -20,看是否多个线程反复访问相邻地址(差值 - 用
perf mem record -e mem-loads -u+perf mem report查“Load Latency”高的内存访问,伪共享常伴随高延迟加载 - 验证优化效果:改完后重点对比
LLC-loads和LLC-load-misses的比值,下降 20% 以上才算有效
真正难的不是加 alignas 或 thread_local,而是判断哪些变量高频写入、哪些只是偶尔读取、哪些其实可以彻底移出共享域——伪共享优化本质是做内存访问的“交通管制”,得清楚每条数据流的起点、终点和拥堵点。


















