返回

CPU缓存的陷阱:你以为的加速器,可能是减速器

Android

揭秘伪共享:多核处理器中的隐形杀手

什么是伪共享?

想象一下,你正在和几个朋友合作完成一项任务。每个朋友都有一块相同大小的白板,上面可以写字。但是,有一个问题:白板不能同时被多人使用。

在多核处理器系统中,伪共享类似于这个场景。当多个处理器核心同时访问同一块内存时,就会发生伪共享。每个核心都有自己的缓存,其中存储了该内存块的副本。然而,当其中一个核心更新内存时,其他核心的缓存副本就过时了。

这就像你的朋友更新了白板上的字迹,而其他朋友却不知道,仍然拿着过时的白板。他们必须重新查看白板才能获取最新信息,这浪费了时间。

伪共享如何发生?

伪共享通常有两种方式:

  • 共享变量位于同一个缓存行中: 缓存行是处理器缓存的基本单位,通常为 64 字节。如果两个共享变量位于同一个缓存行中,则更新其中一个变量时,整个缓存行都会失效。其他核心必须从内存中重新加载整个缓存行,即使他们只访问了其中一个变量。
  • 共享变量被过度填充: 过度填充是指存储在共享变量中的数据量超过了实际需要。这会导致共享变量的大小超过一个缓存行。更新共享变量时,整个缓存行都会失效,其他核心必须从内存中重新加载它。

伪共享的危害

伪共享可不是闹着玩的,它会造成严重后果:

  • 降低性能: 处理器核心必须从内存中重新加载数据,这会增加延迟,降低程序性能。
  • 增加功耗: 重新加载数据需要更多能量,从而增加功耗。
  • 降低稳定性: 过时的缓存数据可能会导致处理器核心产生错误,甚至导致系统崩溃或数据损坏。

如何避免伪共享?

避免伪共享就像给你的朋友分发不同的白板,让他们同时工作而不会互相干扰。以下是几个方法:

  • 使用编译器选项: 编译器提供选项来帮助避免伪共享。例如,GCC 中的 -falign-functions=16 选项将函数对齐到 16 字节边界,防止函数中的局部变量位于同一个缓存行中。
  • 使用内存对齐: 内存对齐是将变量地址对齐到特定大小。这可以通过使用特殊函数来实现,例如 C 语言中的 posix_memalign()
  • 使用缓存行填充: 缓存行填充是在共享变量中添加额外的字节,以确保其大小超过一个缓存行。这有助于防止过度填充。

代码示例

以下是使用 posix_memalign() 进行内存对齐的 C 代码示例:

#include <stdlib.h>

int main() {
  int *x;
  posix_memalign((void **)&x, 64, sizeof(int));  // 将 x 对齐到 64 字节边界

  // 使用 x 就像平常一样...

  free(x);
  return 0;
}

常见问题解答

1. 伪共享对所有多核处理器都有影响吗?

是的,伪共享是多核处理器固有的问题。

2. 我如何检测伪共享?

使用性能分析工具,例如 VTune 或 Perf,可以检测伪共享。它们可以识别缓存未命中和重新加载。

3. 伪共享总是坏事吗?

不一定。在某些情况下,伪共享可以帮助减少内存带宽。但是,在大多数情况下,它对性能是有害的。

4. 避免伪共享的最佳方法是什么?

使用编译器选项、内存对齐和缓存行填充是避免伪共享的有效方法。

5. 伪共享和内存屏障有什么关系?

内存屏障是指令,用于强制处理器按特定顺序执行操作。它们可以帮助防止伪共享,因为它们确保在其他核心看到更新之前,对内存的更新已完成。

结论

伪共享是多核处理器系统中的一个隐形杀手。它会降低性能、增加功耗和降低稳定性。通过理解伪共享的原因和影响,我们可以采取措施避免它,从而释放多核处理器的全部潜力。