多个线程和CPU缓存

我正在使用多个线程在C中实现图像过滤操作,并使其尽可能优化。 我有一个问题:如果一个内存被thread-0访问,并且如果同一个内存被thread-1访问,它是否会从缓存中获取它? 这个问题源于这两个线程可能运行到CPU的两个不同核心的可能性。 因此,另一种方法是:所有内核是否共享相同的公共缓存?

假设我有如下的内存布局

int输出[100];

假设有2个CPU核心,因此我产生两个线程同时工作。 一种方案可以是将内存分成两个块,0-49和50-99,并让每个线程在每个块上工作。 另一种方法可能是让线程0在偶数索引上工作,比如0 2 4等等……而另一个线程工作在奇数索引上,比如1 3 5 ……这个后来的技术更容易实现(特别适用于3D)数据)但我不确定我是否可以通过这种方式有效地使用缓存。

一般来说,分享重叠的内存区域是一个坏主意,就好像一个线程处理0,2,4 ……以及其他进程1,3,5 ……虽然有些架构可能会支持这一点,但大多数架构都不会,您可能无法指定代码将在哪台机器上运行。 此外,操作系统可以自由地将您的代码分配给它喜欢的任何核心(一个,两个在同一物理处理器上,或两个核心在不同的处理器上)。 此外,每个CPU通常都有一个单独的第一级缓存,即使它位于同一个处理器上。

在大多数情况下,0,2,4 … / 1,3,5 ……会使性能降低,甚至可能比单个CPU慢。 Herb Sutters “消除虚假分享”certificate了这一点。

使用方案[… n / 2-1]和[n / 2 … n]将在大多数系统上进行更好的扩展。 它甚至可以导致超线性性能,因为可以使用总和中所有CPU的高速缓存大小。 使用的线程数应始终可配置,并且应默认为找到的处理器核心数。

这个问题的答案很大程度上取决于体系结构和缓存级别,以及线程实际运行的位置。

例如,最近的英特尔多核CPU具有每个核心的L1高速缓存,以及在同一CPU封装中的核心之间共享的二级高速缓存; 但是不同的CPU包将拥有自己的L2缓存。

即使在您的线程在一个软件包中的两个核心上运行的情况下,如果两个线程都访问同一个高速缓存行中的数据,您将在两个L1高速缓存之间弹出该高速缓存行。 这非常低效的,您应该设计算法以避免这种情况。


一些评论询问如何避免这个问题。

从本质上讲,它确实不是特别复杂 – 您只是想避免两个线程同时尝试访问位于同一缓存行上的数据,其中至少有一个线程正在写入数据。 (只要所有线程只读取数据,就没有问题 – 在大多数体系结构中,只读数据可以存在于多个缓存中)。

为此,您需要知道缓存行大小 – 这取决于体系结构,但目前大多数x86和x86-64系列芯片使用64字节缓存行(请参阅其他体系结构的体系结构手册)。 您还需要知道数据结构的大小。

如果您要求编译器将感兴趣的共享数据结构与64字节边界(例如,数组output )对齐,那么您知道它将从高速缓存行的开头开始,并且您还可以计算后续的位置缓存行边界。 如果你的int是4个字节,那么每个缓存行将包含恰好8个int值。 只要数组在高速缓存行边界上开始,那么output[0]output[7]将在一个高速缓存行上,并在下一个output[8]output[15] 。 在这种情况下,您将设计算法,使每个线程处理一个相邻int值的块,该值是8的倍数。

如果要存储复杂的struct类型而不是普通的int ,那么pahole实用程序将是有用的。 它将分析编译后的二进制文件中的struct类型,并显示布局(包括填充)和总大小。 然后,您可以使用此输出调整struct – 例如,您可能需要手动添加一些填充,以便struct是缓存行大小的倍数。

在POSIX系统上, posix_memalign()函数对于分配具有指定对齐的内存块很有用。

我可能会误会,但是核心的缓存是否共享取决于CPU的实现。 您必须在制造商页面上查找技术表,以检查CPU中的每个核心是否都有自己的缓存或是否共享缓存。

我正在为一家安全公司从事图像处理工作,有时我们在线程上运行批处理操作后会出现损坏的图像。 经过长时间的调查,我们得出结论,缓存是在CPU Core之间共享的,在极少数情况下,数据被覆盖或用不正确的数据替换。

无论这是一个需要考虑的事情,还是一个罕见的事件,我无法回答。