2

页面推荐“循环展开”作为优化:

可以通过减少迭代次数和复制循环体来减少循环开销。

例子:

在下面的代码片段中,循环体可以复制一次,迭代次数可以从 100 次减少到 50 次。

for (i = 0; i < 100; i++)
  g ();

下面是循环展开后的代码片段。

for (i = 0; i < 100; i += 2)
{
  g ();
  g ();
}

使用 GCC 5.2,除非您使用,否则不会启用循环展开-funroll-loops(在-O2或中均未启用-O3)。我已经检查了组件,看看是否有显着差异。

g++ -std=c++14 -O3 -funroll-loops -c -Wall -pedantic -pthread main.cpp && objdump -d main.o

版本 1:

   0:   ba 64 00 00 00          mov    $0x64,%edx
   5:   0f 1f 00                nopl   (%rax)
   8:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # e <main+0xe>
   e:   83 c0 01                add    $0x1,%eax
  # ... etc ...
  a1:   83 c1 01                add    $0x1,%ecx
  a4:   83 ea 0a                sub    $0xa,%edx
  a7:   89 0d 00 00 00 00       mov    %ecx,0x0(%rip)        # ad <main+0xad>
  ad:   0f 85 55 ff ff ff       jne    8 <main+0x8>
  b3:   31 c0                   xor    %eax,%eax
  b5:   c3                      retq

版本 2:

   0:   ba 32 00 00 00          mov    $0x32,%edx
   5:   0f 1f 00                nopl   (%rax)
   8:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # e <main+0xe>
   e:   83 c0 01                add    $0x1,%eax
  11:   89 05 00 00 00 00       mov    %eax,0x0(%rip)        # 17 <main+0x17>
  17:   8b 0d 00 00 00 00       mov    0x0(%rip),%ecx        # 1d <main+0x1d>
  1d:   83 c1 01                add    $0x1,%ecx
  # ... etc ...
 143:   83 c7 01                add    $0x1,%edi
 146:   83 ea 0a                sub    $0xa,%edx
 149:   89 3d 00 00 00 00       mov    %edi,0x0(%rip)        # 14f <main+0x14f>
 14f:   0f 85 b3 fe ff ff       jne    8 <main+0x8>
 155:   31 c0                   xor    %eax,%eax
 157:   c3                      retq 

版本 2 产生更多的迭代。我错过了什么?

4

2 回答 2

4

是的,在某些情况下,循环展开会使代码更有效率。

理论是减少较少的开销(分支到循环顶部并增加循环计数器)。

大多数处理器讨厌分支指令。他们喜欢数据处理指令。对于每次迭代,至少有一个分支指令。通过“复制”一组代码,减少了分支的数量,增加了分支之间的数据处理指令。

许多现代编译器都有优化设置来执行循环展开。

于 2015-09-04T18:45:05.307 回答
1

它不会产生更多的迭代;你会注意到调用g()两次的循环运行了一半的次数。(如果您必须拨打g()奇数次怎么办?查找 Duff 的设备。)

在您的清单中,您会注意到汇编语言指令jne 8 <main+0x8>在两者中都出现一次。这告诉处理器回到循环的开始。在原始循环中,这条指令将运行 99 次。在滚动循环中,它只会运行 49 次。想象一下,如果循环体很短,只有一两条指令。这些跳转可能是程序中对性能最关键的部分指令的三分之一甚至一半!(甚至还有一个有用的指令循环:BogoMIPS。但是关于优化的文章是个笑话。)

因此,展开循环会以速度换取代码大小,对吧?没那么快。也许你已经让你的展开循环变得如此之大,以至于循环顶部的代码不再在缓存中,CPU 需要获取它。在现实世界中,了解它是否有帮助的唯一方法是分析您的程序。

于 2015-09-04T20:35:09.710 回答