并行计算中的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
    6
    struct 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
    2
    double *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/
作者
John Doe
发布于
2025年3月13日
许可协议