2011-10-07 87 views
9

当编译器执行循环展开优化时,它是如何确定展开循环的是哪个因素还是展开整个循环?由于这是一种空间性能的折衷,平均而言,这种优化技术在提高程序性能方面效果如何?此外,在什么条件下推荐使用这种技术(即某些操作或计算)?优化编译器如何决定何时展开循环以及展开多少循环?

这不一定是特定于某个编译器。它可以是任何解释概述这种技术背后的想法和实践中观察到的。

+11

您是否在寻找关于编译器优化分析的论文? :) – Jon

+1

我想补充一点:为什么gcc的帮助信息说-funroll-all-loops实际上会让程序运行速度变慢?引用:“执行循环展开的优化,这适用于所有循环,并且通常会使程序运行更慢。” – BlackBear

+0

@Jon,没关系,我只是需要一个很好的答案。 –

回答

8

当编译器执行循环展开优化时,它如何确定展开循环或天气以展开整个循环的是哪个因子。

堆栈消耗和局部性。指令计数。根据展开和内联程序制作/传播优化的能力。环路大小是固定的,还是预计在一定范围内。配置文件输入(如果适用)。可能会从循环体中删除的操作。等等。

由于这是一个平均的空间性能折衷,这种优化技术在提高程序执行效率方面效率如何?

它很大程度上取决于输入(您的程序)。它可能会变慢(不是典型的),也可能会快几倍。编写一个程序以优化运行,这也使优化器能够完成其工作。

而且,在什么条件下是建议使用这种技术(即某些操作或计算)

一般,大量的迭代对非常小的机构,特别是其是网点和具有良好的数据局部性。

如果您想知道该选项是否有助于您的应用,配置文件。

如果您需要的不止于此,您应该留出一些时间来学习如何编写最佳程序,因为这个主题相当复杂。

+0

你有没有关于编写最佳程序的资源的建议? –

+0

这实际上取决于你目前的知识水平和你写的程序......也许你会发现这是一个很好的资源:http://www.agner.org/optimize/ – justin

+0

+1对于贾斯汀的链接。在MASM论坛上发现这一点非常有趣:“不适合心脏不好的人,如果MASM超过了你,请采用服务器端脚本。” –

1

当它是(在我看来)好展开循环:

环短且可能使用的所有变量都在处理器的寄存器。展开变量后“重复”,但仍在寄存器中,因此没有内存(或缓存)的损失。

循环(未知循环unrool数)将至少执行几次或几次,因此有理由将加载整个循环展开到指令高速缓存。

如果循环很短(一次或者少数次的入口),它可能对展开非常有利,因为用于确定是否应该再次执行的代码执行的次数较少。

3

简单分析是计数指令 - 一个2个指令循环展开10次 有11个指令,而不是20个产生11/20的加速。但是现代处理器架构要复杂得多,取决于缓存大小和处理器指令流水线的特性。上面的例子可能运行速度快10倍而不是2x。展开1000x而不是10x也可能会运行得更慢。如果没有针对特定的处理器,编译器(或为它们编写的编译指示)只是猜测。

1

好吧,首先,我不知道编译器如何自动执行此操作。我敢肯定,如果不是100个编译器必须选择的算法,至少有10个。
无论如何它可能是特定于编译器的。

但是,我可以帮助您计算其有效性。

请注意,这种技术通常不会带给您极大的性能提升。
但是在重复的循环计算中,可以给出高百分比的性能。
这是因为通常循环内的函数比循环的条件检查花费更多的计算时间。

所以,让我们说我们有一个简单的循环,以恒定的,因为你是懒得做复制粘贴或者只是认为这会更好看:

for (int i = 0; i < 5; i++) 
{ 
    DoSomething(); 
} 

这里有 INT比较,增量和 DoSomethig()调用。
所以如果DoSomething()比较快,那么我们得到了的操作。
现在,如果你将展开这一点,你就会将其降低到仅5操作:

DoSomething(); 
DoSomething(); 
DoSomething(); 
DoSomething(); 
DoSomething(); 

现在它更容易常数,因此让我们看看它是如何与变量工作:

for (int i = 0; i < n; i++) 
{ 
    DoSomething(); 
} 

这里有ň INT比较,ñ incrementations和ñ DoSomethig()调用= 3N。 现在,我们无法展开它完全,但我们可以通过一个常数因子(ñ预计越高,越要展开的话)展开它:

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 incrementations,和ñ DoSomethig()调用= (1 2/3)* N
我们救了自己(1 1/3)* n作业。这减少了近一半的计算时间。另外一种整洁的展开技巧叫做Duff's device
但它非常适合编译器和语言实现。有些语言实际上会变得更糟。