当编译器执行循环展开优化时,它如何确定展开循环的因素或是否展开整个循环?由于这是空间性能权衡,平均而言,这种优化技术在使程序运行得更好方面效率如何?另外,在什么条件下建议使用这种技术(即某些操作或计算)?
这不必特定于某个编译器。它可以是任何解释,概述这种技术背后的想法以及在实践中观察到的内容。
答案 0 :(得分:10)
堆栈消费和地点。指令很重要。能够根据展开和内联的程序进行/传播优化。循环大小是固定的,还是预期在一定范围内。配置文件输入(如果适用)。可以从循环体中移除的操作。等当编译器执行循环展开优化时,它如何确定展开循环或天气的哪个因素来展开整个循环。
由于平均空间性能权衡,这种优化技术在使程序运行得更好方面效率如何?
这在很大程度上取决于输入(你的程序)。它可以更慢(不典型)或者可以快几倍。编写一个程序以便以最佳方式运行,并使优化程序能够完成其工作。
此外,在什么条件下建议使用这种技术(即某些操作或计算)
通常,在非常小的主体上进行大量迭代,尤其是无分支且具有良好数据局部性的主体。
如果您想知道该选项是否有助于您的应用,个人资料。
如果您需要更多,您应该留出一些时间来学习如何编写最佳程序,因为主题非常复杂。
答案 1 :(得分:3)
简单分析是计算指令 - 一个2指令循环展开10次 有11个指令而不是20个指令加速11/20。但是对于现代处理器架构来说,它要复杂得多;取决于高速缓存大小和处理器指令管道的特性。上面的例子可能会快10倍而不是2倍。也可能展开1000x而不是10x会慢一些。如果不针对特定的处理器,编译器(或者你为它们编写的编译指示)只是在猜测。
答案 2 :(得分:1)
当(在我看来)展开循环时很好:
循环很短,可能所有使用的变量都在处理器寄存器中。展开后,变量“重复”但仍然在寄存器中,因此没有内存(或缓存)损失。
循环(具有未知的循环unrool数)将至少执行几次或十次,因此有理由将整个循环加载到指令缓存中。
如果循环很短(一个或几个指令),那么展开可能非常有利,因为确定是否应该再次执行的代码执行频率较低。
答案 3 :(得分:1)
好的,首先,我不知道编译器是如何自动完成的。而且我很确定编译器必须选择的算法至少有10个,如果不是100个 而且无论如何它可能都是特定于编译器的。
但是,我可以帮助你计算它的有效性。
请注意,此技术通常不会为您带来出色的性能提升 但是在重复循环计算中并且可以提供高百分比的性能 这是因为通常循环内的函数比循环的条件检查需要更多的计算时间。
所以,假设我们有一个带常量的简单循环,因为你懒得做复制粘贴或只是认为看起来会更好:
for (int i = 0; i < 5; i++)
{
DoSomething();
}
此处您有 5 int比较, 5 递增和 5 DoSomethig()调用。
因此,如果DoSomething()相对较快,那么我们就可以进行 15 操作
现在,如果你要展开它,你将把它减少到只有5个操作:
DoSomething();
DoSomething();
DoSomething();
DoSomething();
DoSomething();
现在使用常量更容易,所以让我们看看它如何与变量一起使用:
for (int i = 0; i < n; i++)
{
DoSomething();
}
此处您有 n int比较, n 递增, n DoSomethig()调用= 3n 。 现在,我们无法完全展开它,但我们可以通过一个常数因子展开它(预计 n 越高,我们应该展开的越多):
int i;
for (i = 0; i < n; i = i+3)
{
DoSomething();
DoSomething();
DoSomething();
}
if (i - n == 2)
{
DoSomething(); // We passed n by to, so there's one more left
}
else if (i - n == 1)
{
DoSomething(); //We passed n by only 1, so there's two more left
DoSomething();
}
现在我们在这里你有 n / 3 + 2 int比较, n / 3 递增和 n DoSomethig()调用= (1 2/3)* n 。
我们保存了自己(1 1/3)* n 的操作。这将计算时间减少了近一半。
仅供参考,另一项整洁的展开技术称为 Duff's device 。
但它是特定于编译器和语言实现的。有些语言实际上会更糟糕。