我正在使用多个线程在C中实现图像过滤操作,并使其尽可能优化。我有一个问题:如果一个内存被thread-0访问,并且如果同一个内存被thread-1访问,它是否会从缓存中获取它?这个问题源于这两个线程可能运行到CPU的两个不同核心的可能性。所以另一种方法是:所有内核是否共享相同的公共缓存?
假设我有一个如下所示的内存布局
int output [100];
假设有2个CPU核心,因此我产生了两个并发工作的线程。一种方案可以是将内存分成两个块,0-49和50-99,并让每个线程在每个块上工作。另一种方法是让线程0在偶数索引上工作,比如0 2 4等等。而另一个线程工作在奇数索引上,比如1 3 5 ......这个后来的技术更容易实现(特别是对于3D)数据)但我不确定我是否可以通过这种方式有效地使用缓存。
答案 0 :(得分:20)
这个问题的答案在很大程度上取决于架构和缓存级别,以及线程实际运行的位置。
例如,最近的英特尔多核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()
函数对于分配具有指定对齐的内存块非常有用。
答案 1 :(得分:5)
一般来说,分享重叠的内存区域是一个坏主意,就好像一个线程处理0,2,4 ......以及其他进程1,3,5 ......虽然有些架构可能会支持这一点,但大多数架构都会不,您可能无法指定代码将在哪台机器上运行。此外,操作系统可以自由地将您的代码分配给它喜欢的任何核心(一个,两个在同一物理处理器上,或两个核心在不同的处理器上)。此外,每个CPU通常都有一个单独的第一级缓存,即使它位于同一个处理器上。
在大多数情况下,0,2,4 ... / 1,3,5 ...会使性能降低,甚至可能比单个CPU慢。 Herb Sutters "Eliminate False Sharing"证明了这一点。
使用方案[... n / 2-1]和[n / 2 ... n]在大多数系统上都可以更好地扩展。它甚至可以导致超线性性能,因为可以使用总和中所有CPU的高速缓存大小。使用的线程数应始终可配置,并且应默认为找到的处理器核心数。
答案 2 :(得分:0)
我可能会犯错,但是核心的缓存是否共享取决于CPU的实现。您必须在制造商页面上查找技术表,以检查CPU中的每个核心是否都有自己的缓存,或者缓存是否已共享。
我正在为一家安全公司从事图像处理工作,有时候我们在线程上运行批处理操作后会出现损坏的图像。经过长时间的调查,我们得出结论,缓存是在CPU Core之间共享的,在极少数情况下,数据被覆盖或用不正确的数据替换。
这是否值得考虑,或者是一种罕见的事件,我无法回答。
答案 3 :(得分:0)
英特尔文档
英特尔发布了per-generation datasheets,其中可能包含此类信息。
例如,对于我在旧计算机上安装的处理器i5-3210M,我查看了3rd generation - Datasheet Volume 1 3.3“英特尔超线程技术(英特尔HT技术)”:
处理器支持英特尔超线程技术(英特尔HT技术) 允许执行核心充当两个逻辑处理器。虽然有些 共享诸如缓存,执行单元和总线之类的执行资源,每个资源 逻辑处理器具有自己的体系结构状态,具有自己的一组通用寄存器和控制寄存器。
确认在该代CPU的给定超线程中共享缓存。
另请参阅: