循环展开和优化

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】:

好吧,这段代码是“更好”还是“更差”完全取决于ABC 的实现,您期望n 的哪些值,您正在使用哪个编译器以及哪个硬件你正在运行。

通常循环展开的好处是减少执行循环的开销(即增加i 并将其与n 进行比较)。在这种情况下,可以减少 3 倍。

【讨论】:

【参考方案4】:

只要函数 A()、B() 和 C() 不修改相同的数据集,第二个版本就提供了更多的并行化选项。

在第一个版本中,三个函数可以同时运行,假设没有相互依赖关系。在第二个版本中,所有三个函数都可以同时使用所有三个数据集运行,前提是您有足够的执行单元来执行此操作,并且没有相互依赖关系。

【讨论】:

【参考方案5】:

通常,尝试“发明”优化不是一个好主意,除非您有确凿的证据表明您将获得提升,因为很多时候您最终可能会引入降级。通常,获得此类证据的最佳方法是使用良好的分析器。我会使用分析器测试此代码的两个版本以查看差异。

另外,很多时候循环展开不是很便携,如前所述,它很大程度上取决于平台、编译器等。

您还可以使用编译器选项。一个有趣的 gcc 选项是“-floop-optimize”,您可以使用“-O、-O2、-O3 和 -Os”自动获得它

编辑此外,查看“-funroll-loops”编译器选项。

【讨论】:

另外,看看这个相当简洁但令人惊叹的循环展开示例:Duff's device

以上是关于循环展开和优化的主要内容,如果未能解决你的问题,请参考以下文章

前端性能优化:循环优化二,循环展开

JVM优化之循环展开(附有详细的汇编代码)

循环展开与循环平铺

为啥 clang 无法展开循环(即 gcc 展开)?

SSE Intrinsics 和循环展开

循环展开对内存绑定数据的影响