【问题标题】:Is decremental for loop more efficient than incremental? [duplicate]递减循环比递增循环更有效吗? [复制]
【发布时间】:2012-07-18 00:14:24
【问题描述】:

可能重复:
Is it faster to count down than it is to count up?

例如,

for (int i = 0; i < max; i++)
{
    ...
}

和

for (int i = max-1; i >= 0; i--)
{
    ...
}

这两个循环本质上是相同的,并且假设循环不包含任何数组操作。但是,对于第一种情况,每次迭代都需要将 max 加载到处理器中的寄存器中,然后在 i 和 max 之间进行比较。另一方面,后一种情况不需要将 0 加载到寄存器中,因为 0 已经有一个寄存器,所以只有后一个循环的比较。如果我错了,请纠正我,如果我是对的,请详细说明。 谢谢。

【问题讨论】:

  • 如果有任何区别,我会感到非常惊讶。不过,请不要花时间寻找这样的小东西来将您的应用程序加速几毫秒。
  • for (int i = max; i--;) 会比 for (int i = max-1; i &gt;= 0; i--) 更惯用,因为它适用于无符号索引类型。
  • 是的,它可能快了一纳秒。那很重要吗?现代 CPU 每秒执行数十亿条指令,我们可能会节省一条。
  • 对其进行基准测试并告诉我们。哦,如果它有足够的寄存器并且可以确保它永远不会改变,那么编译器很有可能不会溢出max。
  • 它们都是 x86 上的一个汇编指令,所以没有区别。

标签: c++ loops processor


【解决方案1】:

省略号表示的代码几乎肯定会将任何实际性能差异归结为仅仅是噪音。但是,您的所有假设并不正确。

每次迭代都需要将 max 加载到处理器中的寄存器中,然后在 i 和 max 之间进行比较

也许,但也许不是。这取决于您的代码,但任何健全的优化编译器都将能够检测计数器是否在迭代之间发生变化。

我不确定您的一些想法是从哪里得到的,但它们有点误导,并且没有考虑优化编译器的工作原理。看看你的反汇编,看看你自己的真正区别是什么。哦,到底是什么,我会做的(反正很有趣):

程序是:

int main(int argc, char *argv[]){   
    int max = 10;
    for (int i = max-1; i >= 0; i--)
    {
        cout << i;
    }
    return 0;
}

生成的程序集(VS2010版本,我自己的cmets)是:

int main(int argc, char *argv[]){   
00341000  push        esi  
    int max = 10;
    for (int i = max-1; i >= 0; i--)
00341001  mov         esi,9               ; move a static 9 into esi
00341006  jmp         main+10h (341010h)  
00341008  lea         esp,[esp]           ; load the address of whatever
0034100F  nop                             ; esp points to in memory 
    {                                     ; (not a memory fetch, just address calculation)
        cout << i;
00341010  mov         ecx,dword ptr [__imp_std::cout (342048h)]  
00341016  push        esi  
00341017  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (342044h)]  
0034101D  dec         esi                 ; decrement counter
0034101E  jns         main+10h (341010h)  ; jump if not signed
    }

对于更惯用的版本...

int main(int argc, char *argv[]){   
00AC1000  push        esi  
    int max = 10;
    for (int i = 0; i < max; i++)
00AC1001  xor         esi,esi  
    {
        cout << i;
00AC1003  mov         ecx,dword ptr [__imp_std::cout (0AC2048h)]  
00AC1009  push        esi  
00AC100A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0AC2044h)]  
00AC1010  inc         esi               ; increment esi
00AC1011  cmp         esi,0Ah           ; compare to 10 (0Ah)
00AC1014  jl          main+3 (0AC1003h) ; if less, jump to top 
    }

所以是的,第一个版本使用jns 指令(如果没有签名则跳转),因此比较简化了一点(与0相比)。它还包含更多说明,但没有比较。

但是,请注意,在版本 2 中进行的比较也是静态的。它知道max 在整个循环中不会改变,所以它可以相应地优化那个位。

但是我要强烈重申,这不太可能产生明显的性能优势。即使是我的 Windows PC 上的高性能计时器也无法在两者之间提供良好的统计差异,因为对 cout 的调用比循环指令花费的时间要长得多。

【讨论】:

    【解决方案2】:

    编译器级别的优化会根据循环的内容而变化很大,这可能是一个有争议的问题。例如,编译器可能会发现 Max 的值在循环期间不会改变,因此只会将其加载到内存中一次。

    如果您真的担心这种级别的微优化,您需要准确了解您计划使用的编译器和编译器设置,然后在目标硬件上进行时序测试以比较不同的选项。或者,您可以直接查看编译器输出并比较实际的汇编或机器级指令,看看一个版本使用的指令是否比另一个版本多。

    【讨论】:

      【解决方案3】:

      增加或减少的选择通常不基于任何与性能相关的内容。它通常基于对循环上下文最有意义的任何算法或代码位的逻辑流程。

      根据实现,预增量 (++i) 可能有时 比后增量 (i++) 快,但编译器会优化大多数循环,甚至如果迭代次数为持续的。在您发现测试中的性能瓶颈之前,通常不值得优化任何代码。

      简而言之,不要为小事出汗。

      【讨论】:

      • 我认为问题不是关于前增量与后增量,而是关于循环向前与向后计数。
      • 没错,它不会增加答案。由于 OP 对效率的兴趣,我只是添加了它。
      【解决方案4】:

      是的,它可能会更好,因为与 0 比较优于与非零比较。但是现代编译器通常在优化代码方面做得很好,所以差别不大。

      最后一点 - 这是一个微优化。我会避免它,除非它使代码更具可读性。

      【讨论】:

        猜你喜欢
        • 2013-05-04
        • 2012-01-31
        • 2016-04-15
        • 2018-10-08
        • 2014-02-03
        • 1970-01-01
        • 2012-10-07
        • 2017-03-02
        • 2017-07-05
        相关资源
        最近更新 更多