这种优化是否重要?

时间:2015-09-04 18:08:25

标签: c++ optimization g++ compiler-optimization loop-unrolling

page建议“循环展开”作为优化:

  

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

     

示例:

     

在下面的代码片段中,可以复制循环体   一次,迭代次数可以从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生成 更多 迭代。我错过了什么?

2 个答案:

答案 0 :(得分:4)

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

该理论减少了开销(分支到循环顶部和递增循环计数器)。

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

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

答案 1 :(得分:0)

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

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

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