最近我遇到了一个与Intel TBB可扩展分配器相关的问题。基本的用法模式如下:
- 分配几个大小为
N * sizeof(double)
的向量 - 生成一个随机整数
M
,使得M >= N / 2 && M <= N
。 - 访问每个向量的前
M
个元素。 - 重复第2步1000次。
我将M
设置为随机值,因为我不想为固定长度进行性能基准测试。相反,我想在向量长度范围内获得平均性能。
程序的性能因N
的不同值而大不相同。这并不罕见,因为我正在测试的函数是为大型N
优化的。然而,当我试图评估性能和N
之间的关系时,我发现当N
从1016
增加到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字节对齐不足够呢?