我试图了解是什么导致了缓存未命中,以及它们最终会在我们的应用程序性能方面造成多少损失。但是对于我现在所做的测试,我很困惑。
假设我的 L3 缓存是 4MB,我的 LineSize 是 64 字节,我希望这个循环(循环 1):
int8_t aArr[SIZE_L3];
int i;
for ( i = 0; i < (SIZE_L3); ++i )
{
++aArr[i];
}
...和这个循环(循环 2):
int8_t aArr[SIZE_L3];
int i;
for ( i = 0; i < (SIZE_L3 / 64u); ++i )
{
++aArr[i * 64];
}
给出大致相同数量的末级缓存未命中,但不同数量的包含性末级缓存引用。
但是,Visual Studio 2013 分析器提供给我的数字令人不安。
使用循环 1:
- 包含末级缓存引用:53,000
- 最后一级缓存未命中:17,000
使用循环 2:
- 包含末级缓存引用:69,000
- 最后一级缓存未命中:35,000
我已经使用一个动态分配的数组对此进行了测试,并在具有更大的 L3 缓存 (8MB) 的 CPU 上进行了测试,我在结果中得到了类似的模式。
为什么我没有得到相同数量的缓存未命中,为什么我在更短的循环中有更多引用?
最佳答案
分别递增 int8_t aArr[SIZE_L3];
的每个字节已经足够慢了,以至于硬件预取器可能在很多时候都能保持良好状态。乱序执行可以让大量的读-修改-写同时运行到不同的地址,但最好的情况仍然是每个存储时钟一个字节。 (存储端口 uops 的瓶颈,假设这是系统上的单线程测试,对内存带宽没有很多其他要求)。
英特尔 CPU have their main prefetch logic in L2 cache (如英特尔优化指南中所述;请参阅 x86 标签 wiki)。因此,在内核发出负载之前成功将硬件预取到 L2 缓存中意味着 L3 缓存永远不会丢失。
John McCalpin's answer on this Intel forum thread确认 L2 硬件预取未被正常性能事件(如 MEM_LOAD_UOPS_RETIRED.LLC_MISS
)计为 LLC 引用或未命中。显然,您可以查看 OFFCORE_RESPONSE
事件。
IvyBridge introduced next-page HW prefetch .之前的 Intel 微体系结构在预取时不会跨越页面边界,因此您仍然每 4k 就会丢失一次。如果操作系统没有机会主义地将您的内存放入 2MiB 的大页面中,TLB 可能会丢失。 (但是当您接近页面边界时,推测性页面遍历可能会避免那里的很多延迟,并且硬件肯定会进行推测性页面遍历)。
对于 64 字节的跨度,执行接触内存的速度比缓存/内存层次结构能够跟上的速度快得多。您在 L3/主内存上遇到瓶颈。乱序执行可以同时保持大约相同数量的读取/修改/写入操作,但相同的乱序窗口覆盖 64 倍的内存。
更详细地解释确切的数字
对于 L3 左右的数组大小,IvyBridge's adaptive replacement policy可能会产生重大影响。
在我们知道确切的 uarch 和测试的更多细节之前,我不能说。目前尚不清楚您是否只运行了该循环一次,或者您是否有一个外部重复循环并且那些未命中/引用数字是每次迭代的平均值。
如果它仅来自单次运行,那是一个微小的噪声样本。我假设它在某种程度上是可重复的,但令我惊讶的是,对于每字节版本,L3 引用计数如此之高。 4 * 1024^2/64 = 65536
,因此您接触的大部分缓存行仍然有一个 L3 引用。
当然,如果您没有重复循环,并且这些计数包括代码在循环之外所做的所有事情,那么这些计数中的大部分可能来自您程序中的启动/清理开销。 (即你的循环被注释掉的程序可能有 48k L3 引用,IDK。)
I have tested this with a dynamically allocated array
完全不足为奇,因为它仍然是连续的。
and on a CPU that has a larger L3 cache (8MB) and I get a similar pattern in the results.
这个测试是否使用了更大的阵列?或者您是否在具有 8MiB L3 的 CPU 上使用了 4MiB 阵列?
关于c++ - 更短的循环,相同的覆盖范围,为什么我在使用 Visual Studio 2013 的 C++ 中得到更多的末级缓存未命中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38341426/