这个循环优化配置文件意味着什么?

时间:2015-05-14 04:59:26

标签: c++ assembly optimization

我开始使用VTune。作为一个教育示例,我在调试模式下尝试了一些微优化。这是我的代码库中的玩具示例。此代码显示在C ++非const方法中," .data_length"是对象的int字段(偏移32字节),通常是一个大数字:

for (int i=0;i<data_length;++i) { /*...*/ }

VTune向我展示了for循环的汇编(来自MSVC 2013)。请注意性能数字,以秒为单位(我删除了所有未注册的时间)。我还添加了一些注释:

0x140433084 mov dword ptr [rsp+0x588], 0x0 |       | ;"i=0"
0x14043308f jmp 0x1404330a1 <Block 77>     |       | ;jump to compare and loop body
                                           |       |
0x140433091 Block 76:                      |       | ;"++i"
0x140433091 mov eax, dword ptr [rsp+0x588] | 0.451 |
0x140433098 inc eax                        | 0.002 |
0x14043309a mov dword ptr [rsp+0x588], eax |       |
                                           |       |
0x1404330a1 Block 77:                      |       | ;if (!(i<data_length)) goto next section
0x1404330a1 mov rax, qword ptr [rsp+0x6f0] | 0.407 |
0x1404330a9 mov eax, dword ptr [rax+0x20]  |       | ;  move "data_length" into "eax".
0x1404330ac cmp dword ptr [rsp+0x588], eax | 1.195 | ;  "i<data_length;"
0x1404330b3 jnl 0x140433106 <Block 80>     |       |
0x1404330b5 Block 78:                      |       |
. . .                                      |       | ;Loop body.  There's a jmp in here to
                                           |       | ;  block 76.
                                           |       |
0x140433106 Block 80:                      |       | ;code following loop

这告诉我的是,加载i来增加它会导致缓存失败(为什么不是寄存器,geez?)。其次,测试逻辑非常缓慢 - 特别是加载&#34; .data_length&#34;每一次。

我想,为什么不加载一次然后使用递减:

for (int i=data_length-1;i>=0;--i) { /*...*/ }

装配和时间看起来像:

0x140433084 mov rax, qword ptr [rsp+0x6f0] |       | ;Same code, but now only happens once!
0x14043308c mov eax, dword ptr [rax+0x20]  |       |
0x14043308f dec eax                        |       | ;"data_length-1"
0x140433091 mov dword ptr [rsp+0x588], eax |       | ;"i=data_length-1;"
0x140433098 jmp 0x1404330aa <Block 77>     |       | ;jump to compare and loop body
                                           |       |
0x14043309a Block 76:                      |       | ;"++i"
0x14043309a mov eax, dword ptr [rsp+0x588] | 0.357 |
0x1404330a1 dec eax                        | 0.002 |
0x1404330a3 mov dword ptr [rsp+0x588], eax |       |
                                           |       |
0x1404330aa Block 77:                      |       | ;if (i<0) goto next section
0x1404330aa cmp dword ptr [rsp+0x588], 0x0 | 0.401 | ;  "i>=0;"
0x1404330b2 jl 0x140433105 <Block 80>      | 2.806 |
0x1404330b4 Block 78:                      |       |
. . .                                      |       | ;Loop body.  Same as above, I think.
                                           |       |
0x140433105 Block 80:                      |       | ;code following loop

看看jl!跳三秒钟?我想也许位置不在指令缓存中,但是你可以看到它实际上非常接近(正如你所期望的那样在循环体之后)。更重要的是,第一种方法无论如何都应该有同样的问题。第一个版本的jnl甚至没有注册。

我的猜测是它的时间在循环体中被吃掉了 - 虽然它在一个案例中发生但在另一个案例中却不是很奇怪。我还有更多的工作要做吗?

我写了这一切,现在再看一遍,我认为这可能是一个无聊的分支预测问题。 CPU喜欢在循环中向后移动分支,但在这种情况下,阻止80 的分支不应该在大多数情况下被占用。

我肯定还在学习这个,所以假设我对所有内容进行了大致正确的注释,我有几个问题:

  1. 我是否正确地认为i应该是一个寄存器,并且它将成为优化模式中的一个?
  2. 第二版中的jl发生了什么?确实是分支预测失败了吗?为什么它不显示在下一条指令上?
  3. 编辑:正在测试的CPU是Intel 990X(Gulftown,2011)。

1 个答案:

答案 0 :(得分:0)

android:fitsSystemWindows应该在寄存器中(例如,如果编译器已优化);然而,循环体中的代码(未示出)也可能使用所有寄存器,并且它可以更好地保证性能,以避免将i放入寄存器(特别是如果该循环体)包含内部循环或函数调用或其他东西)。

我不知道你的CPU是什么(VIA,AMD,Intel; Atom,Xeon;多大年纪),但现代CPU中的分支预测应该能很好地处理这个分支。但是,当循环终止时,您可能期望分支误预测,并且如果迭代次数较少(例如i很小),则单个误预测可能很重要。