2016-12-01 98 views
2

最近我遇到了英特尔TBB可扩展分配器的问题。基本使用模式是作为以下,缓存对齐英特尔CPU上的内存分配

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

我将M设为随机,因为我不想将基准性能定为固定长度。相反,我想获得一系列矢量长度的平均性能。

对于不同的值N,程序性能差别很大。这并不罕见,因为我测试的功能旨在优先考虑大型N的性能。但是,当我试图对性能和N之间的关系进行基准测试时,我发现在某个点上,当N1016增加到1017时,会有两倍的差异。

我的第一个直觉是N = 1016的性能变差与较小的向量大小无关,而是它有一定的缓存。很有可能存在虚假分享。品尝下的功能使用SIMD指令,但不使用堆栈内存。它从第一个向量中读取一个32字节的元素,并在计算之后,它将第二个(和第三个)向量写入32个字节。如果发生虚假共享,可能会丢失几十个周期,这正是我观察到的性能损失。一些分析证实了这一点。

本来我将每个向量与32字节边界对齐,用于AVX指令。为了解决这个问题,我将矢量与64字节的边界对齐。但是,我仍然观察到相同的性能损失。通过128字节对齐解决问题。

我做了一些更多的挖掘。英特尔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   
---------------------------------------------------------------------------------------------------- 

此外,英特尔TBB的源代码,128个字节对齐的特点是评论这是为了向后兼容。

那么为什么在我的情况下64字节对齐不够?

回答

2

您遇到conflict misses。 从1016到1017时发生的原因是,您开始使用关联列表中的最后一个缓存行。

您的缓存是32K 8路,因此每个套件都是4K。您的64字节缓存行可以容纳8个双打。 但你的1017-1024矢量使用8K而不是4K? 1024 * sizeof(double),以及你使用N/2-> N,这样你的每个向量的使用(除了正好N/2)一些相同的低地址位组合。

在使用完所有L1缓存之前,您不会遇到冲突命中的问题,您现在正在使用您的所有L1缓存。使用1个矢量读取和2个矢量写入,全部长8K,所以使用24K,如果在计算过程中使用8K以上的额外数据,您将获得越来越大的驱逐您选择的数据的机会。

介意你只使用矢量的第一部分,但它们冲突永远不会少。

当你从1016跳到1017时,你将能够观察到L1缓存缺失的增加。当你超过1024倍时,性能损失应该会在短时间内消失,直到你达到L1缓存容量缺失。

<成像这里的曲线图,显示当所有8个集用于>

从由乌利齐·德雷珀神奇物品在一个尖峰:“Memory part 5: What programmers can doenter image description here

+0

感谢您的详细说明。这是我知道的,尽管解释得更清楚。然而,如果不增加分配的内存大小,是否有办法避免这种冲突呢?我的印象是,通过对齐63字节的边界足以避免两个向量在大多数情况下共享相同的缓存行。相反,我观察到需要128字节的对齐方式 –

+0

感谢您的图表和链接。我会先阅读一下。 –

+0

对齐并不重要,因为它是set-associativesnes,这是问题在这里,意思是向量使用的总内存。 – Surt