循环展开和优化
Posted
技术标签:
【中文标题】循环展开和优化【英文标题】:Loop unrolling & optimization 【发布时间】:2012-04-09 21:56:18 【问题描述】:给定代码:
for (int i = 0; i < n; ++i)
A(i) ;
B(i) ;
C(i) ;
以及优化版本:
for (int i = 0; i < (n - 2); i+=3)
A(i)
A(i+1)
A(i+2)
B(i)
B(i+1)
B(i+2)
C(i)
C(i+1)
C(i+2)
我不清楚:哪个更好?我看不到使用其他版本可以更快地工作的任何东西。我在这里错过了什么吗?
我所看到的是每条指令都取决于前一条指令,这意味着 我需要等待上一条指令完成才能在...之后开始下一条指令
谢谢
【问题讨论】:
***有一篇关于循环展开背后想法的好文章:en.wikipedia.org/wiki/Loop_unwinding 一般来说,这些是不等价的。应该是A(i);双); C(一); A(i+1);乙(i+1);等 【参考方案1】:在语言的高级视图中,您不会看到优化。速度提升来自于编译器对你所拥有的东西所做的事情。
在第一种情况下,它类似于:
LOCATION_FLAG;
DO_SOMETHING;
TEST FOR LOOP COMPLETION;//Jumps to LOCATION_FLAG if false
第二个是这样的:
LOCATION_FLAG;
DO_SOMETHING;
DO_SOMETHING;
DO_SOMETHING;
TEST FOR LOOP COMPLETION;//Jumps to LOCATION_FLAG if false
您可以看到在后一种情况下,测试和跳转的开销仅为每 3 条指令 1 条指令。第一种是每 1 条指令;所以它发生得更频繁。
因此,如果您有可以依赖的不变量(使用您的示例的 mod 3 数组),那么展开循环会更有效,因为底层程序集是更直接地编写的。
【讨论】:
【参考方案2】:循环展开用于减少跳转和分支指令的数量,这可能会使循环更快,但会增加二进制文件的大小。根据实现和平台,两者都可能更快。
【讨论】:
【参考方案3】:好吧,这段代码是“更好”还是“更差”完全取决于A
、B
和C
的实现,您期望n
的哪些值,您正在使用哪个编译器以及哪个硬件你正在运行。
通常循环展开的好处是减少执行循环的开销(即增加i
并将其与n
进行比较)。在这种情况下,可以减少 3 倍。
【讨论】:
【参考方案4】:只要函数 A()、B() 和 C() 不修改相同的数据集,第二个版本就提供了更多的并行化选项。
在第一个版本中,三个函数可以同时运行,假设没有相互依赖关系。在第二个版本中,所有三个函数都可以同时使用所有三个数据集运行,前提是您有足够的执行单元来执行此操作,并且没有相互依赖关系。
【讨论】:
【参考方案5】:通常,尝试“发明”优化不是一个好主意,除非您有确凿的证据表明您将获得提升,因为很多时候您最终可能会引入降级。通常,获得此类证据的最佳方法是使用良好的分析器。我会使用分析器测试此代码的两个版本以查看差异。
另外,很多时候循环展开不是很便携,如前所述,它很大程度上取决于平台、编译器等。
您还可以使用编译器选项。一个有趣的 gcc 选项是“-floop-optimize”,您可以使用“-O、-O2、-O3 和 -Os”自动获得它
编辑此外,查看“-funroll-loops”编译器选项。
【讨论】:
另外,看看这个相当简洁但令人惊叹的循环展开示例:Duff's device以上是关于循环展开和优化的主要内容,如果未能解决你的问题,请参考以下文章