Cache aligned memory allocation on Intel CPU

4

最近我遇到了一个与Intel TBB可扩展分配器相关的问题。基本的用法模式如下:

  1. 分配几个大小为N * sizeof(double)的向量
  2. 生成一个随机整数M,使得M >= N / 2 && M <= N
  3. 访问每个向量的前M个元素。
  4. 重复第2步1000次。

我将M设置为随机值,因为我不想为固定长度进行性能基准测试。相反,我想在向量长度范围内获得平均性能。

程序的性能因N的不同值而大不相同。这并不罕见,因为我正在测试的函数是为大型N优化的。然而,当我试图评估性能和N之间的关系时,我发现当N1016增加到1017时,存在两倍的性能差异。

我的第一反应是,N = 1016的性能退化与较小的向量大小无关,而与缓存有关。很可能存在false sharing。被测试的函数使用SIMD指令,但不使用堆栈内存。它从第一个向量读取一个32字节元素,并在计算后将32字节写入第二个(和第三个)向量。如果发生false sharing,则可能会损失几十个周期,这正是我观察到的性能惩罚。一些分析证实了这一点。

最初,我使每个向量与32字节边界对齐,以适应AVX指令。为了解决问题,我将向量与64字节边界对齐。然而,我仍然观察到相同的性能惩罚。通过128字节对齐可以解决问题。

我进行了更深入的挖掘。Intel TBB有一个cache_aligned_allocator。在其源代码中,内存也是按128字节对齐的。

这就是我不理解的地方。如果我没有错的话,现代x86 CPU的缓存行大小为64字节。CPUID确认了这一点。以下是正在使用的CPU的基本缓存信息,从我使用CPUID检查功能的小程序中提取出来的:

Vendor    GenuineIntel
Brand     Intel(R) Core(TM) i7-4960HQ CPU @ 2.60GHz

====================================================================================================
Deterministic Cache Parameters (EAX = 0x04, ECX = 0x00)
----------------------------------------------------------------------------------------------------
Cache level                             1           1           2           3           4           
Cache type                              Data        Instruction Unified     Unified     Unified     
Cache size (byte)                       32K         32K         256K        6M          128M        
Maximum Proc sharing                    2           2           2           16          16          
Maximum Proc physical                   8           8           8           8           8           
Coherency line size (byte)              64          64          64          64          64          
Physical line partitions                1           1           1           1           16          
Ways of associative                     8           8           8           12          16          
Number of sets                          64          64          512         8192        8192        
Self initializing                       Yes         Yes         Yes         Yes         Yes         
Fully associative                       No          No          No          No          No          
Write-back invalidate                   No          No          No          No          No          
Cache inclusiveness                     No          No          No          Yes         No          
Complex cache indexing                  No          No          No          Yes         Yes         
----------------------------------------------------------------------------------------------------

此外,在Intel TBB的源代码中,128字节对齐是由一个注释标记的,说明这是为了向后兼容。那么为什么在我的情况下,64字节对齐不足够呢?
1个回答

4
你被冲突错过打中了。 当你从1016到1017时发生这种情况,是因为你开始使用关联列表中的最后一个缓存行。
你的缓存是32K 8路组相联,所以每个集合是4K。你的缓存行的64字节可以容纳8个双精度数。 但是你的1017-1024向量使用了8K而不是4K???1024*sizeof(double),嗯,你使用的是N/2->N,所以对于每个向量,除非恰好是N/2,否则会重复使用一些相同的低地址位组合。
只有在使用完所有L1缓存之后才会出现冲突命中的问题,而你现在已经接近使用完毕。如果你在计算过程中使用1个读取向量和2个长度为8K的写入向量,总共使用24K,如果你在计算过程中使用超过8K的额外数据,你将越来越有可能驱逐你选择的数据。
请注意,你只使用了向量的第一部分,但它们仍然存在冲突。
当您从1016到1017时,L1缓存未命中次数会增加,这一点您可以观察到。当超过1024个双精度数时,性能惩罚应该会短暂消失,直到出现L1缓存容量不足的情况。
在Ulrich Drepper的精彩文章“内存第5部分:程序员可以做什么”中,有一个图表显示当使用所有8个集合时会出现峰值。enter image description here

感谢详细的解释。这让我更清楚了解到我已经知道的内容。但是,有没有一种方法可以避免冲突而不增加分配的内存大小?我曾经认为通过将向量对齐到63字节边界就足以避免两个向量在大多数情况下共享同一缓存行。然而,我观察到需要128字节的对齐方式。 - Yan Zhou
谢谢提供图表和链接。我会先阅读一下相关内容。 - Yan Zhou
对齐方式并不重要,因为问题在于集合关联性,这意味着向量使用的总内存。 - Surt
谢谢。如果我理解正确,128字节对齐的原因似乎使事情变得更好仅仅是因为它导致了一些填充,使内存分配变大。 - Yan Zhou
128字节对齐是2个缓存行,在某些情况下,这使得它更好,因为在缓存架构的某个地方,会获取两个缓存行而不是一个。但是,在这种特殊情况下,由于冲突缺失,该优势被覆盖。 - Surt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接