【问题标题】:How can a program's size increase the rate of cache misses?程序的大小如何增加缓存未命中率?
【发布时间】:2017-01-15 17:17:03
【问题描述】:

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

增加了程序代码大小,这可能是不可取的,尤其是对于嵌入式应用程序。还可能导致指令缓存未命中的增加,这可能会对性能产生不利影响。

这是为什么?

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

【问题讨论】:

    标签: caching memory cpu-architecture


    【解决方案1】:

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

    那么我们为什么要展开(如果我们有记忆力的话)?

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

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

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

    【讨论】:

      【解决方案2】:

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

      • 增加了程序代码大小,这可能是不可取的,尤其是对于嵌入式应用程序。还可能导致指令缓存未命中的增加,这可能会对性能产生不利影响。

      • 如果循环体中的代码涉及函数调用,则可能无法将展开与内联结合起来,因为代码大小的增加可能过多。因此,可以在两种优化之间进行权衡。

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

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

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

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

      【讨论】:

        【解决方案3】:

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

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

        与死代码不同,循环展开导致使用过的代码增加缓存未命中率。

        【讨论】:

        • 配置文件引导优化是编译器查找热代码和冷代码的好方法。 (许多程序的代码在启动时运行,但之后再也不会运行。将所有冷代码组合在一起,使其不与热代码混合是一件好事,即使它实际上不是死代码。)
        • 这本身并没有错,但增加的代码大小是循环展开会导致更多 i-cache 未命中的主要原因,我认为这个答案并没有足够强调这一点。
        猜你喜欢
        • 1970-01-01
        • 2018-02-16
        • 2018-09-11
        • 2013-06-29
        • 1970-01-01
        • 2013-04-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多