程序的大小如何增加高速缓存未命中率?

How can a program's size increase the rate of cache misses?

维基百科在其循环展开文章中有以下声明:

Increased program code size, which can be undesirable, particularly for embedded applications. Can also cause an increase in instruction cache misses, which may adversely affect performance.

这是为什么?

此外,由于大量死代码而导致程序的代码大小变大不会增加缓存未命中率,因为死代码不会被执行?

代码通常以整个 缓存行 的形式读入缓存,可能是 64、128 或 256 字节。如果你有一个 256 字节的缓存行,其中四分之三是死代码,那么你就没有很好地使用你的缓存内存。另一方面,如果您有 1 兆字节的完全未使用的代码,那根本不会影响缓存效率。

一些编译器会使用软件开发人员的启发式或提示来找出哪些代码可能很少使用,并安排代码,以便一个缓存行将完全填充已使用的代码或完全填充未使用的代码。

而且与死代码不同,使用过的代码因循环展开而膨胀增加缓存未命中率。

loop unrolling 上的维基百科文章列出了此技术的几个可能的缺点,其中两个与代码大小有关:

  • Increased program code size, which can be undesirable, particularly for embedded applications. Can also cause an increase in instruction cache misses, which may adversely affect performance.

  • If the code in the body of the loop involves function calls, it may not be possible to combine unrolling with inlining, since the increase in code size might be excessive. Thus there can be a trade-off between the two optimizations.

循环展开增加静态代码大小,因为它复制了循环中的部分代码。希望它会 减少 动态指令计数,因为需要执行的循环迭代次数更少。

对于小型循环体,循环体中指令数量的增加可能不会对指令缓存命中率产生负面影响。但是随着循环体变大,增加的代码大小可能会导致指令缓存中其他有用的行被逐出,并且整体命中率可能会降低。这主要是因为执行的代码量较大。

当循环体包含函数调用并且编译器必须确定是否内联函数时,循环展开变得特别复杂。或者,如果循环是可能在其他地方内联的函数的一部分。内联和循环展开都有可能减少动态指令数,但它们也会增加静态代码大小。因此,编译器通常使用启发式方法来选择如何组合这些优化,因为问题无法得到最佳解决(至少不能在合理的时间内解决)。

如其他答案对齐问题之一所述,也可能导致更多的指令缓存未命中。然而,循环展开可能导致更多缓存未命中的主要原因是它增加了循环体中实际执行的代码量(以及额外的序言和结尾代码)。

展开循环使它们更快,但代价是额外的指令缓存未命中,并且当我们有指令缓存未命中时,我们无法安排任何有用的代码。

那么我们为什么还要展开(如果我们有足够的内存来展开)?

因为如果我的展开 4 次导致循环在 1000 次迭代中 运行 快 25%,那么在循环 1 次缓存未命中后支付成本的速度仍然更快。

因此,您必须查看使用更多代码的相对成本与缓存未命中的额外成本。

额外的代码也会导致一些额外的 TLB 未命中,但该论点仍然成立。