维基百科在其循环展开文章中有以下声明:
程序代码大小增加,这可能是不可取的,特别是对于嵌入式应用程序。也可能导致指令缓存未命中增加,这可能会对性能产生负面影响。
为什么会这样?
此外,由于大量死代码导致程序的代码大小变大,因此不会增加缓存未命中率,因为死代码不会被执行?
答案 0 :(得分:3)
代码通常读入整个缓存行中的缓存,可能是64,128或256字节。如果你有一个256字节的缓存行,其中四分之三是死代码,那么你就没有很好地使用你的缓存。另一方面,如果你有一兆字节的完全未使用的代码,那根本不会影响缓存效率。
一些编译器将使用软件开发人员的启发式或提示来找出可能很少使用的代码,并安排代码以便一个缓存行将完全填充使用的代码或完全填充未使用的代码。
与死代码不同,使用循环展开膨胀的代码将增加缓存未命中。
答案 1 :(得分:1)
关于loop unrolling的维基百科文章列出了这种技术的几个可能的缺点,其中两个与代码大小有关:
程序代码大小增加,这可能是不可取的,特别是对于嵌入式应用程序。也可能导致指令缓存未命中的增加,这可能会对性能产生负面影响。
如果循环体中的代码涉及函数调用,则可能无法将展开与内联组合,因为代码大小的增加可能过多。因此,可以在两种优化之间进行权衡。
循环展开增加静态代码大小,因为它复制了循环中的部分代码。希望它减少动态指令计数,因为需要执行更少的循环迭代。
对于小循环体,循环体中增加的指令数可能不会对指令缓存命中率产生负面影响。但随着循环体变大,增加的代码大小可能导致指令高速缓存中的其他有用行被驱逐,并且整体命中率可能会降低。这主要是因为执行的代码量较大。
当循环体包含函数调用并且编译器必须确定是否内联函数时,循环展开变得特别复杂。或者,如果循环是可能在其他地方内联的函数的一部分。内联和循环展开都有可能减少动态指令数量,但它们也会增加静态代码大小。因此编译器通常使用启发式方法来选择如何组合这些优化,因为问题无法以最佳方式解决(至少在合理的时间内没有)。
正如其他一个答案中所提到的,对齐问题也可能导致更多的指令缓存未命中。但是,循环展开的主要原因可能导致更多的缓存未命中,因为它增加了循环体中实际执行的代码量(以及附加的序言和结尾代码)。
答案 2 :(得分:0)
展开循环使它们更快,但是以额外的指令缓存未命中为代价,并且当我们有指令缓存未命中时,我们无法安排任何有用的代码。
那么为什么我们要打开(如果我们有记忆的话)?
因为如果我的4次展开导致循环在1000次迭代中运行速度提高25%,那么在1次缓存未命中循环后,它仍然可以更快地支付成本。
因此,您必须考虑使用更多代码的相对成本,而不是高速缓存未命中的额外成本。
额外的代码也会导致一些额外的TLB未命中但参数仍然存在。