哪个循环更有效率?增量还是减量?

14

可能是重复问题:
倒数比正数循环更快吗?

哪种循环性能更好?我从某个地方学到第二种循环更好但想知道原因。

  for(int i=0;i<=10;i++)
      {
               /*This is better ?*/
      }


  for(int i=10;i>=0;i--)
      {
               /*This is better ?*/
      }

1
你测试过它们中的任何一个吗? - 一二三
6
你的循环边界不同:第一个从0到9运行,第二个从10递减到1。 - kkm
6个回答

23

第二种方法“可能”更好,因为与0比较i比与10比较i更容易,但我认为您可以使用任何一种方法,因为编译器会对它们进行优化。


为什么将i与0进行比较比与10进行比较更容易? - 一二三
2
给新手一些宽容,因为他的答案是正确的。 - Stuart Golodetz
11
因为大多数处理器都有比较到零的指令,所以它们不需要加载“i”和10,然后将它们相减并与零进行比较,而是可以直接将“i”与零进行比较。 - PhoenixS
4
因为他没有任何理由就被减一了,所以我给他加一。 - Unai Vivi
3
@funnyGraphicsThingy,因为在检测零作为算术或逻辑结果时通常不需要显式的比较 - CPU 零标志位已经设置。此外,许多指令集提供循环/重复操作,可以自动倒计一个寄存器值到零。+1 给 OP。 - Martin James
显示剩余2条评论

8

我认为两个循环的性能差别不大。

但是,当循环像这样时,情况就不同了。

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

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

假设getMaximum()函数不是内联函数,因为它会被调用一次或多次


6

如果硬件进行了优化,将递减循环计数器直到零有时可能更快。但这只是微小的优化,你应该进行性能分析,看看是否值得这样做。编译器通常会为您进行优化,而且由于递减循环可能表达意图更差,因此您通常最好坚持使用“正常”的方法。


5

在汇编指令中,递增和递减(翻译为INC和DEC)的速度相同,都是1个CPU周期。

然而,在一些体系结构(例如SPARC)上,理论上第二种方法可以更快,因为不需要从内存(或高速缓存)中获取10:大多数体系结构都有能够以优化方式处理与特殊值0进行比较的指令(通常有一个特殊的硬连线0寄存器用作操作数,因此不必为每个迭代的比较存储10),

聪明的编译器(特别是目标指令集为RISC时)会自行检测这一点,并且(如果您的计数器变量未在循环中使用)应用第二个“递减到0”的形式。

有关详细信息,请参见答案https://dev59.com/53E85IYBdhLWcg3wViA4#2823164https://dev59.com/53E85IYBdhLWcg3wViA4#2823095


在超标量架构中谈论一条指令的执行时间是没有意义的。桌面处理器已经使用超标量技术约15年了(1993年的奔腾处理器就采用了超标量技术)。 - AProgrammer
并非所有现代硬件都是流水线处理的。嵌入式处理器将对这种微观优化具有更大的相关性。此外,OP的问题并没有特别涉及现代台式机处理器,因此我认为答案应该是通用的,并且还应该涉及20年前的处理器设计(这些处理器通常仍然用作嵌入式CPU)。 - Unai Vivi
问题在于答案是“取决于”。它取决于处理器ISA是否更好地支持其中一种。它取决于微架构能否利用ISA支持。如果编译器也能够利用(并且无法采取对策,例如将10保留在寄存器中,看到循环执行了已知的少量次数并完全展开它或使用辅助递减计数器)。有太多变量。测量和上下文中的测量是唯一的答案,并且对所有应用程序都有效。 - AProgrammer
你说得没错。然而,考虑到硬件类型、编译器类型或版本没有指定任何组合,我宁愿指出一些通用的东西,对于这样很少的组合来说更快(同时对于没有的组合来说更慢),而不是关注于毫无疑问地在第一时间做这些超出上下文的微基准测试是没有多大意义的事情。 - Unai Vivi

2
编译器应该优化两个代码到相同的汇编代码,因此它们没有区别。两者花费的时间相同。
更有意义的讨论是:是否...
  for(int i=0;i<10;++i)  //preincrement
  {
  }

会比...更快
  for(int i=0;i<10;i++)  //postincrement
  {
  }

理论上,后缀自增会执行额外的操作(返回旧值的引用)。但是,即使如此,它也应该被优化为相同的汇编代码。

如果没有优化,代码将如下所示:

   for ( int i = 0; i < 10 ; i++ )
0041165E  mov         dword ptr [i],0 
00411665  jmp         wmain+30h (411670h) 
00411667  mov         eax,dword ptr [i] 
0041166A  add         eax,1 
0041166D  mov         dword ptr [i],eax 
00411670  cmp         dword ptr [i],0Ah 
00411674  jge         wmain+68h (4116A8h) 

   for ( int i = 0; i < 10 ; ++i )
004116A8  mov         dword ptr [i],0 
004116AF  jmp         wmain+7Ah (4116BAh) 
004116B1  mov         eax,dword ptr [i] 
004116B4  add         eax,1 
004116B7  mov         dword ptr [i],eax 
004116BA  cmp         dword ptr [i],0Ah 
004116BE  jge         wmain+0B2h (4116F2h) 

   for ( int i = 9; i >= 0 ; i-- )
004116F2  mov         dword ptr [i],9 
004116F9  jmp         wmain+0C4h (411704h) 
004116FB  mov         eax,dword ptr [i] 
004116FE  sub         eax,1 
00411701  mov         dword ptr [i],eax 
00411704  cmp         dword ptr [i],0 
00411708  jl          wmain+0FCh (41173Ch) 

因此,即使在这种情况下,速度也是相同的。


应该优化为相同的汇编代码...如果循环内部使用了“i”会怎样? - Benoit
@Benoit 我的意思是指相同数量的指令。 - Luchian Grigore

1

再次强调,所有微性能问题的答案都是测量,在使用环境的情况下进行测量,不要推广到其他环境。

很长一段时间以来,计算指令执行时间一直需要非常复杂的技术才能实现。

处理器和内存速度之间的不匹配以及缓存的引入(但不包括带宽)使得一组指令的执行非常敏感于内存访问模式。这意味着您仍然可以通过相当高级的思考来优化,但也意味着如果不考虑内存访问模式,则表面上更糟糕的东西一旦考虑到这一点就可能更好。

然后超标量(处理器可以同时执行多个操作)和乱序执行(处理器可以在流程中先于前一个指令执行指令)使基本计数即使忽略内存访问也毫无意义。如果您想获得良好的先验估计,您必须知道哪些指令需要执行(因此忽略部分结构是不明智的)以及处理器如何分组指令。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接