并行计算中的false sharing
本篇主要讨论了 “False Sharing”(伪共享) 这个导致并行计算扩展性差的性能问题。
1. 什么是 False Sharing?
- 现代 CPU 使用 缓存行(Cache Line) 作为数据传输的最小单位,通常是 64 字节 。
- 当不同的线程操作 独立的变量 ,但这些变量恰好落在同一个缓存行时,就会发生 False Sharing。
- 每当一个线程更新它的变量,整个缓存行都会被标记为无效,导致 其他线程不得不重新从内存加载数据 ,降低并行性能。
2. 图解 False Sharing
- 左图 :多个线程(HW thread 0 和 HW thread 1)访问
sum[0]、sum[1]、sum[2]、sum[3],这些变量紧挨着存储在同一缓存行中。 - 右图 :其他线程(HW thread 2 和 HW thread 3)也在操作相同数组的不同索引,导致缓存行在多个 CPU 核心之间不断来回交换,影响性能。
3. 为什么 False Sharing 导致扩展性差?
- 由于多个线程修改同一个缓存行中的不同变量,缓存系统会不停地进行 “Cache Coherency”(缓存一致性) 维护。
- 这种反复的 缓存失效(cache invalidation) 和 缓存同步 开销很大,导致程序的 并行扩展性(scalability) 变差。
4. 解决方案
✅ 方法:使用 “Padding”(填充)
- 在数组元素之间插入额外的字节(通常是一个缓存行大小,如 64 字节),确保每个线程访问的变量不会共享同一缓存行。
- 代码示例:
1
2
3
4
5
6struct PaddedSum {
double value;
char padding[64]; // 使结构体大小等于一个 cache line
};
PaddedSum sum[NUM_THREADS]; // 避免 false sharing
✅ 方法:使用 Thread-Local Storage
- 让每个线程使用自己的局部变量,而不是共享数组:
1
double local_sum = 0; // 每个线程独立维护一个局部 sum
✅ 方法:使用 aligned_alloc() 进行内存对齐
- 通过
posix_memalign()或aligned_alloc()保证变量对齐到缓存行大小:1
2double *sum;
posix_memalign((void**)&sum, 64, NUM_THREADS * sizeof(double));
5. 总结
- False Sharing 发生在多个线程操作 相邻的变量 ,但这些变量 共享同一缓存行 ,导致性能下降。
- 主要影响:
- CPU 缓存一致性协议(MESI)导致大量的缓存无效化操作。
- 线程间的同步开销增大,降低并行扩展性。
- 解决方案:
- 使用 Padding ,使不同线程访问的数据落在不同缓存行中。
- 使用线程局部变量 ,减少共享数据。
- 手动控制内存对齐 ,避免多个变量落在同一缓存行。
这一问题在 OpenMP、MPI、SPMD 并行计算 中尤为常见,因此优化 False Sharing 可以大幅提高程序的并行性能! 🚀
并行计算中的false sharing
http://example.com/2025/03/13/并行计算中的false-sharing/