【问题标题】:Is ++i really faster than i++ in for-loops in java?在 java 的 for 循环中,++i 真的比 i++ 快吗?
【发布时间】:2011-06-17 10:25:23
【问题描述】:

在 java 中,我通常会创建一个如下的 for 循环:

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

但最近一位同事这样打字:

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

他说后者会更快。这是真的吗?

【问题讨论】:

  • 我认为您将难以衡量实际差异。通过优化循环内部部分通常可以获得更好的结果;)
  • 可能有,但太少了,你甚至都不会注意到。像这样的微优化完全没用。
  • @PeeHaa:两个循环运行相同数量的循环。我尝试使用 max =10 并且 i 的值都从 0 变为 9。
  • @Linus Kleen 这些问题获得大量关注并不奇怪 - 程序员往往喜欢技巧和调整,以及他们正在编写强大而快速的代码的想法......大多数程序员学习编码,然后必须学会不要过度优化他们的代码。

标签: java loops performance premature-optimization


【解决方案1】:

即使是这样,我非常怀疑,你的同事真的应该有比如何优化循环表达式更好的东西来花时间学习。

【讨论】:

  • 看,妈!如果我只使用字母表前半部分的字母作为变量名,我的程序运行得更快!到重构机!
  • @Rafe Kettler 如果这是真的,那就太可怕了。 :)
  • 可怕的是,在许多编译器中,较短的变量名使用较少的内存。我们可以整天沉迷于微小的黑客攻击,但我的处理器无缘无故地没有 7.3 亿个晶体管。
  • @Rafe Kettler 这不是我听过的最疯狂的理论,该奖项必须授予这样一种理念:如果硬盘驱动器上有更多信息,它会更重。
  • @biziclop 如果你把报纸放在上面
【解决方案2】:

不,这不是真的。您可以通过为每个循环计时大量迭代来衡量性能,但我相当肯定它们将是相同的。

这个神话来自 C,其中++i 被认为比i++ 更快,因为前者可以通过递增 i 然后返回它来实现。后者可以通过将 i 的值复制到临时变量、递增 i、然后返回临时变量来实现。第一个版本不需要制作临时副本,因此很多人认为它更快。但是,如果将表达式用作语句,则现代 C 编译器可以将临时副本优化掉,这样在实践中就没有区别了。

【讨论】:

  • 感谢您的精彩解释。
  • 现代编译器确实会对此进行优化,但是如果您使用 C++,并且 i 是一个对象(例如,一个交互器),并且这些运算符不是内联的,++i 将比 i++ 快。
  • 另外,如果你去参加工作面试,你应该说它更快 :) 但是如果你开始在 c++ 中使用迭代器,就没有 it++ 这样的东西了。而且你会因为一遍又一遍地收到同样愚蠢的错误消息而不停地撞墙。像我一样x)
  • @BarnabasSzabolcs 后增量可以为迭代器定义,您只是没有使用正确的(或错误的,取决于您的观点)。
【解决方案3】:

对于任何能力合理的优化器,它们将完全相同。如果您不确定,请查看输出字节码或对其进行分析。

【讨论】:

    【解决方案4】:

    它不会更快。编译器和带有 JIT 的 JVM 将把这些微不足道的差异变成肉泥。

    您可以使用常用的循环优化技术来获得速度优势,例如展开(如果适用)。

    【讨论】:

      【解决方案5】:

      即使它会更快,没有人在乎在 HotSpot 的日子里。 JIT 做的第一件事是删除所有 javac 所做的优化。之后,一切都留给 JIT 来加快速度。

      【讨论】:

      • [需要引用] HotSpot 删除所有 javac 优化
      • 优化包括删除或重组事物,所以没有什么可以“删除”。
      【解决方案6】:

      在 Java 中应该没有区别 - 任何现代编译器* 在这两种情况下都应该生成相同的字节码(只是一个iinc),因为没有使用增量表达式的结果直接。
      还有第三种选择,还是一样的字节码*

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

      *用 Eclipse 的编译器测试过

      【讨论】:

        【解决方案7】:

        不,根本没有区别。

        这来自 C++,但即使在这种情况下也没有任何区别。 不同之处在于 i 是一个对象。 i++ 必须制作对象的额外副本,因为它必须返回项目的原始未更改值,而 ++i 可以返回更改后的对象,因此保存副本。

        在具有用户定义对象的 c++ 中,复制的成本可能很高,因此绝对值得记住。正因为如此,人们也倾向于将它用于 int 变量,因为无论如何它都一样好......

        【讨论】:

          【解决方案8】:

          使用“javap -c YourClassName”反编译并查看结果并据此决定。通过这种方式,您可以看到编译器在每种情况下实际做了什么,而不是您认为它做了什么。通过这种方式,您还可以了解为什么一种方式比另一种方式更快。

          【讨论】:

            【解决方案9】:

            这个问题需要一些 Java 字节码。考虑以下代码:

            public class PostPre {
                public static void main(String args[]) {
                    int n = 5;
                    loop1(n);
                    loop2(n);
                }
            
                public static void loop1(int n) {
                    for (int i = 0; i < n; i++) {}
                }
            
                public static void loop2(int n) {
                    for (int i = 0; i < n; ++i) {}
                }
            }
            

            现在编译和反汇编它:

            $ javac PostPre.java; javap -c PostPre.class 
            Compiled from "PostPre.java"
            public class PostPre {
              public PostPre();
                Code:
                   0: aload_0       
                   1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                   4: return        
            
              public static void main(java.lang.String[]);
                Code:
                   0: iconst_5      
                   1: istore_1      
                   2: iload_1       
                   3: invokestatic  #2                  // Method loop1:(I)V
                   6: iload_1       
                   7: invokestatic  #3                  // Method loop2:(I)V
                  10: return        
            
              public static void loop1(int);
                Code:
                   0: iconst_0      
                   1: istore_1      
                   2: iload_1       
                   3: iload_0       
                   4: if_icmpge     13
                   7: iinc          1, 1
                  10: goto          2
                  13: return        
            
              public static void loop2(int);
                Code:
                   0: iconst_0      
                   1: istore_1      
                   2: iload_1       
                   3: iload_0       
                   4: if_icmpge     13
                   7: iinc          1, 1
                  10: goto          2
                  13: return        
            }
            

            loop1()loop2() 具有相同的字节码。

            【讨论】:

              【解决方案10】:

              在你的环境中试试这个

              public class IsOptmized {
                  public static void main(String[] args) {
              
                      long foo; //make sure the value of i is used inside the loop
                      long now = 0; 
                      long prefix = 0;
                      long postfix = 0;
              
                      for (;;) {
                          foo = 0;
                          now = System.currentTimeMillis();
                          for (int i = 0; i < 1000000000; i++) {
                              foo += i;
                          }
                          postfix = System.currentTimeMillis() - now;
              
                          foo = 0;
                          now = System.currentTimeMillis();
                          for (int i = 0; i < 1000000000; ++i) {
                              foo += i;
                          }
                          prefix = System.currentTimeMillis() - now;
              
                          System.out.println("i++ " + postfix + " ++i " + prefix + " foo " + foo);
                      }
                  }
              }
              

              我的给我

              i++ 1690 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1600 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1611 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1691 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1600 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1691 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1691 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1692 ++i 1610 foo 499999999500000000
              i++ 1701 ++i 1610 foo 499999999500000000
              i++ 1691 ++i 1610 foo 499999999500000000
              

              所以即使不是那么多,我认为还是有区别的

              【讨论】:

                【解决方案11】:

                在 Java 中没有这样的区别。 Java机器对代码进行交互,无论你写的是++i还是i++,它都会被转换成字节码到完全相同的指令集。

                但在 C/C++ 中存在巨大差异,如果您不使用任何优化标志,那么您的循环可能会慢 3 倍。

                使用 -O/-O3 等优化标志将强制编译器使输出汇编代码更简单(在大多数情况下),因此更快(在大多数情况下)。

                【讨论】:

                  猜你喜欢
                  • 2013-04-26
                  • 2014-08-09
                  • 2015-07-05
                  • 2019-04-26
                  • 2012-01-13
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多