最近我遇到了英特尔TBB可扩展分配器的问题 . 基本使用模式如下,
-
分配了一些大小为
N * sizeof(double)
的向量 -
生成一个随机整数
M
,使M >= N / 2 && M <= N
. -
访问每个向量的第一个
M
元素 . -
重复步骤2. 1000次 .
我将 M
设置为随机,因为我不想基于固定长度的基准性能 . 相反,我希望在一系列矢量长度上获得平均性能 .
对于 N
的不同值,程序性能差别很大 . 这并不罕见,因为我正在测试的功能旨在优先考虑大型 N
的性能 . 然而,当我试图对性能与 N
之间的关系进行基准测试时,我发现在某一点上,当 N
从 1016
增加到 1017
时,存在两倍的差异 .
我的第一直觉是 N = 1016
的性能退化与较小的矢量大小无关,而是有一些事情要做缓存 . 最有可能是虚假分享 . 品尝下的功能使用SIMD指令,但没有堆栈内存 . 它从第一个向量中读取一个32字节的元素,并在计算之后将32个字节写入第二个(和第三个)向量 . 如果发生错误共享,可能会丢失几十个周期,这正是我观察到的性能损失 . 一些分析证实了这一点 .
最初我将每个向量与32字节边界对齐,用于AVX指令 . 为了解决这个问题,我将向量与64字节边界对齐 . 但是,我仍然观察到相同的性能损失 . 对齐128字节解决问题 .
我做了一些挖掘 . 英特尔TBB有 cache_aligned_allocator
. 在其源中,内存也以128个字节对齐 .
这就是我没有't understand. If I am not mistaken, modern 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字节对齐不够呢?
1 回答
你被conflict misses击中 . 从1016到1017时发生的原因是您开始使用关联列表中的最后一个缓存行 .
你的缓存是32K 8路,所以每套都是4K . 您的64字节缓存行可以容纳8个双精度数 . 但你的1017-1024矢量使用8K而不是4K ??? 1024 * sizeof(双),你使用N / 2-> N,所以你使用(除了恰好N / 2)一些相同的低地址位组合每个矢量两次 .
在您使用了所有L1缓存之前,您不会遇到冲突命中问题,而您现在已经接近了 . 使用1个向量进行读取,使用2个向量进行写入,所有8K长,因此使用24K,如果在计算过程中使用8K额外数据,则会增加驱逐所选数据的机会 .
请注意,您只使用向量的第一部分,但它们之间的冲突从未如此 .
当你从1016跳到1017时,你将能够观察到L1缓存未命中的增加 . 当你超过1024倍时,性能损失会在短时间内消失,直到你达到L1缓存容量未命中 .
<在此处对图形进行成像,显示当使用所有8组时的尖峰>
来自Ulrich Drepper的精彩文章:“Memory part 5: What programmers can do”