【问题标题】:for loop optimizationfor循环优化
【发布时间】:2011-08-30 21:57:27
【问题描述】:
List<String> flowers = new ArrayList<String>();

我的 for 循环目前看起来像这样......

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

或者我应该把它改成下面给出的代码

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

哪个性能更好(假设我有一大堆鲜花),我猜应​​该是后者。

【问题讨论】:

  • 我认为底层机制可能会为您优化。
  • 希望 java 编译器为您进行优化。
  • 如果 Java 没有进行优化,那么是的,后者最好避免每次都调用 .size() 。但是 Java 相当聪明。
  • @sje397, @Yaur:它可能会也可能不会优化它。这取决于 JVM 循环内的代码。如果里面的代码改变了flowers 的大小(这可能很难检查!)那么它不能被优化。
  • @sje397 不,它无法优化,因为 size() 可能有副作用

标签: java coding-style optimization


【解决方案1】:

最好使用 for-each 循环 [更具可读性]

for (Flower flower :flowers){
    //...
}

我已经使用javap 转储了以下代码的指令:

public void forLoop1() {
    List<String> lst = new ArrayList<String>();
    for (int i = 0; i < lst.size(); i++) {
        System.out.println("hi");
    }
}

public void forLoop2() {
    List<String> lst = new ArrayList<String>();
    int size = lst.size();
    for (int i = 0; i < size; i++) {
        System.out.println("hi");
    }
}

public void forLoop1();
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   iconst_0
   9:   istore_2
   10:  iload_2
   11:  aload_1
   12:  invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   17:  if_icmpge       34
   20:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   23:  ldc     #6; //String hi
   25:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   28:  iinc    2, 1
   31:  goto    10
   34:  return

public void forLoop2();
  Code:
   0:   new     #2; //class java/util/ArrayList
   3:   dup
   4:   invokespecial   #3; //Method java/util/ArrayList."<init>":()V
   7:   astore_1
   8:   aload_1
   9:   invokeinterface #4,  1; //InterfaceMethod java/util/List.size:()I
   14:  istore_2
   15:  iconst_0
   16:  istore_3
   17:  iload_3
   18:  iload_2
   19:  if_icmpge       36
   22:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
   25:  ldc     #6; //String hi
   27:  invokevirtual   #7; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
   30:  iinc    3, 1
   33:  goto    17
   36:  return

它不适合我。

java 版本“1.6.0_22”Java(TM) SE 运行时环境(构建 1.6.0_22-b04) Java HotSpot(TM) 客户端虚拟机(内部版本 17.1-b03,混合模式, 分享)

因此,如果您需要从上述两个中进行选择,请选择第二个,但我个人会选择 for-each


每个性能

来自 Joshua Bloch 的 Effective Java 中的第 46 项:

for-each 循环,在 发布 1.5,摆脱混乱 和出错的机会 隐藏迭代器或索引变量 完全地。结果成语 同样适用于集合和 数组:

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    doSomething(e);
}

当您看到冒号 (:) 时,将其读作 “在。”因此,上面的循环读作 “对于元素中的每个元素 e。”笔记 没有性能损失 使用 for-each 循环,甚至 for 数组。事实上,它可能会提供轻微的 性能优于普通 在某些情况下 for 循环,因为它 计算数组索引的限制 只有一次。虽然您可以通过 手(第 45 项),程序员不会 总是这样做。


另见

【讨论】:

  • -1:这不是问题,解释哪个结构最好,这只是一个评论......
  • 这不是一个好的答案。 Java 的 for each loop 是 OPs 问题的两倍,是优化循环的 4 倍。
  • +1 它一个很好的答案,因为当你的整体表现不佳时,你应该做的最后一件事就是做那种微-优化。性能问题通过多线程和查看复杂性来解决(O-Notation,包括选择合适的集合类型)。一个坚实的 +1 可读性。有时,考虑微调会有所帮助。 (一般性声明,乔可能有充分的理由提出他的问题)
  • 它不适合我。 -- 仅供参考,javac 做的优化很少。此类优化由 JIT 编译器完成。
  • 多么可靠的答案!!!我正忙于有效的 Java。多么棒的阅读。唯一要检查的附加事项是花是否为空。但不适用于上面的例子。
【解决方案2】:

抱歉,@Jigar 的回答不正确。这是正确的答案。 (tldr;不要使用for : each)。

import java.util.ArrayList;
import java.util.List;

public class LoopTest {

    public static void main(String s[]) {

        long start, end;

        List<Integer> a =  new ArrayList<Integer>();

        for (int i = 0; i < 2500000; i++) {
            a.add(i);
        }

        ///// TESTING FOR : EACH LOOP

        start = System.currentTimeMillis();

        for (Integer j : a) {
            int x = j + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ Integer j : a ] ");

        ////// TESTING DEFAULT LOOP

        start = System.currentTimeMillis();
        for (int i = 0; i < a.size(); i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < a.length; i++ ] ");


        ////// TESTING SLIGHTLY OPTIMIZED LOOP

        start = System.currentTimeMillis();
        int size = a.size();
        for (int i = 0; i < size; i++) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = 0; i < size; i++ ] ");        

        //// TESTING MORE OPTIMIZED LOOP

        start = System.currentTimeMillis();
        for (int i = size; --i >= 0;) {
            int x = a.get(i) + 3;
        }

        end = System.currentTimeMillis();

        System.out.println(end - start
                + " milli seconds for [ int i = size; --i >= 0; ] ");       

    }

}

结果:

96 milli seconds for [ Integer j : a ] 
57 milli seconds for [ int i = 0; i < a.length; i++ ] 
31 milli seconds for [ int i = 0; i < size; i++ ] 
31 milli seconds for [ int i = size; --i >= 0; ] 

您可以自己决定,但过多归因于 JVM 优化器。您仍然必须对自己的代码保持聪明,并且使用for : each 表示法不是一个好主意(几乎永远不会)。如您所见,将 size 放入自己的变量中是个好主意。

尽管其中一些优化可能依赖于 JVM(有些可能与 JIT 一起发挥作用),但了解 Java 做什么和 Java 不做什么也很重要。

【讨论】:

  • 你对 for : each 总是很慢,频繁调用 .size() 比分配给一个变量要慢一些
  • 说“使用 for-each 表示法不是一个好主意(几乎永远)”是非常愚蠢的。在绝大多数情况下,您会更喜欢可读性而不是性能。在您的循环是性能瓶颈的少数情况下,调查是否有更高性能的选项可能是值得的。
  • 我认为每个示例代码的问题在于自动装箱和拆箱。
  • for each 还创建了一个迭代器——堆中的一个对象,所以很多 for each 可以增加内存压力。
  • @dfa - 你知道这个答案已经有 10 年历史了,对吧?而且它比 JMH 早了几年?
【解决方案3】:

JVM 无法对其进行优化,因为size() 是一种方法,并且JVM 不能(也不会尝试)确定size() 在此上下文中将始终返回相同的值。如果size() 的值没有改变,第二个的性能会稍微好一些,但增益是如此之小,以至于您甚至不必考虑使用它。

【讨论】:

    【解决方案4】:

    如果性能至关重要,请使用普通的计数器循环,但是对于 98% 的情况,代码的清晰性和简单性更为重要(例如 1000 倍或更多),您应该使用 for-each 循环。

    @David 指出使用计数器更快,但我要指出,即使对于 10,000 个条目,差异也是亚微秒。

    如果您有超过 10,000 个条目的集合,那么您很可能不应该蛮力地遍历所有可能性。对于您所想的任何事情,具有像 Map 这样的查找功能的集合更有可能是一个更好的解决方案。如果您的条目远少于 10,000 个,那么性能就不太重要了。

    【讨论】:

      【解决方案5】:

      如果数组列表在迭代时发生变化,则行为会有所不同。但我猜你不会那样做。根据我的测试,后者通常更快(特别是在 Android 等系统上)。我会这样写:

      for (int i = 0, size = flowers.size(); i < size; i++) {
          ...
      }
      

      【讨论】:

        【解决方案6】:

        来自 Java 语言规范 (14.14.1):

        基本的for语句执行一些初始化代码,然后执行一个 表达式、语句和一些更新代码重复直到 表达式为假。

        在您的第一个示例中,表达式为 i &lt; flowers.size(),并且在每次迭代中都会评估一次。在您的特殊情况下,它不应该有明显的区别,因为flowers.getSize() 上的ArrayList 是一个非常短的方法。但是,一般情况下,如果每次迭代的表达式的结果都相同且代价高昂,那就做一个预计算。

        结果:这必须在 Java 虚拟机的每个实现中产生相同的输出,并证明 Expression 在每次迭代中计算一次:

        int counter2 = 10;
        for (int counter1 = 0; counter1 < counter2; counter1++) {
          System.out.println(counter1 + ", " + counter2);
          counter2--;
        }
        

        输出:

        0, 10
        1, 9
        2, 8
        3, 7
        4, 6
        

        【讨论】:

          【解决方案7】:

          最好的选择是

          [ int i = 0; i < size; i++ ]
          

          您的结果将根据 JVM 和其他设置(如 -client 与 -server)而有所不同 因为某些测量值非常小,您需要使用纳秒来测量,并且您需要进行许多测试,否则最终会导致 GC 弄乱结果。这些类型的测试也有 JVM 将循环优化为空的习惯。我试图通过将它在代码末尾修改的变量放到屏幕上来消除这种风险。

          1.6
          -server
          7.968242071 milli seconds for [ Integer j : a ] 
          7.206275775999999 milli seconds for [ int i = 0; i < a.length; i++ ]  
          1.5864E-5 milli seconds for [ int i = 0; i < size; i++ ] 
          14.774186076999998 milli seconds for [ int i = size; --i >= 0; ] 
          
          -client
          83.36101683999999 milli seconds for [ Integer j : a ] 
          44.288568631 milli seconds for [ int i = 0; i < a.length; i++ ]  
          2.3191E-5 milli seconds for [ int i = 0; i < size; i++ ] 
          24.826621246 milli seconds for [ int i = size; --i >= 0; ] 
          
          1.7
          
          -server
          7.029150422 milli seconds for [ Integer j : a ] 
          6.6269827779999995 milli seconds for [ int i = 0; i < a.length; i++ ]  
          1.3852E-5 milli seconds for [ int i = 0; i < size; i++ ] 
          13.842110377 milli seconds for [ int i = size; --i >= 0; ] 
          13.868426141 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
          1.6618000000000003E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 
          
          -client
          7.382479727 milli seconds for [ Integer j : a ] 
          6.748068759 milli seconds for [ int i = 0; i < a.length; i++ ]  
          1.4162999999999998E-5 milli seconds for [ int i = 0; i < size; i++ ] 
          13.951547335999999 milli seconds for [ int i = size; --i >= 0; ] 
          13.929234053999998 milli seconds for [ int i = a.size()-1; i >= 0; i-- ] 
          1.6873E-5 milli seconds for [ int i = 0; i < a.size(); i++ ] 
          

          测试代码:

          public static void main(String s[]) {
          long start=0, end = 0, delta = 0;
          //int[] a = new int[2500000];
          List<Integer> a = new ArrayList<Integer>();
          int x = 0;
          
          for (int i = 0; i < 2500000; i++) {
              a.add(i);
          }
          
          start=0; end = 0; delta = 0;
          for (int ctr = 0; ctr < 1000; ctr++) {
              start = System.nanoTime();
              for (Integer j : a) {
                   x = j + 3;
              }
              end = System.nanoTime();
              delta += end - start;
          }
          System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ Integer j : a ] ");
          
          
          start=0; end = 0; delta = 0;
          for (int ctr = 0; ctr < 1000; ctr++) {
              start = System.nanoTime();
              for (int i = 0; i < a.size(); i++) {
                   x = a.get(i) + 3;
              }
              end = System.nanoTime();
              delta += end - start;
          }
          System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.length; i++ ]  ");
          
          int size = a.size();
          
          start=0; end = 0; delta = 0;
          for (int ctr = 0; ctr < 1000; ctr++) {
              start = System.currentTimeMillis();
          
              for (int i = 0; i < size; i++) {
                   x = a.get(i) + 3;
              }
              end = System.currentTimeMillis();
              delta += end - start;
          }
          System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < size; i++ ] ");
          
          start=0; end = 0; delta = 0;
          for (int ctr = 0; ctr < 1000; ctr++) {
              start = System.nanoTime();
              for (int i = size; --i >= 0;) {
                   x = a.get(i) + 3;
              }
              end = System.nanoTime();
              delta += end - start;
          }
          System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = size; --i >= 0; ] ");
          
          
          start=0; end = 0; delta = 0;
          for (int ctr = 0; ctr < 1000; ctr++) {
              start = System.nanoTime();
              for (int i = a.size()-1; i >= 0; i--) {
                   x = a.get(i) + 3;
              }
              end = System.nanoTime();
              delta += end - start;
          }
          System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = a.size()-1; i >= 0; i-- ] ");
          
          start=0; end = 0; delta = 0;
          for (int ctr = 0; ctr < 1000; ctr++) {
              start = System.currentTimeMillis();
          
              for (int i = 0; i < a.size(); i++) {
                   x = a.get(i) + 3;
              }
              end = System.currentTimeMillis();
              delta += end - start;
          }
          System.out.println(Math.pow(10, -6) * delta / 1000 + " milli seconds for [ int i = 0; i < a.size(); i++ ] ");        
          
          System.out.println(x);
          }
          

          【讨论】:

          • 很好的数据收集。但有些问题: 1.不要切换使用的定时器! (它们返回不同的单位,因此报​​告的值不正确!) 2. 更新一致的结果和格式,因此所有数字都使用相同的基数 3. 在同一过程中多次运行整组测试以检查对于异常。 “最快”也不一定是“最好”;-)
          【解决方案8】:

          这只是通过示例来说明这是多么的情景。

          我测试了“正常”for 循环 for (int i = 0; i &lt; list.size(); i++) 和微优化 for 循环 for (int i = -1, size = list.size(); ++i &lt; size;) 的执行。我从命令行在 Eclipse 中运行了测试,发现了巨大的差异。

          eclipse中运行的结果:

          Time for Original: 32552 ms   Time for MicroOptimized 32707 ms
          Fastest Loop: Original
          Slowest loop takes 0.47616121897272057% more time
          

          从命令行运行的结果:

          Time for Original: 274489 ms   Time for MicroOptimized 30516 ms
          Fastest Loop: MicroOptimized
          Slowest loop takes 799.4920697339101% more time
          

          所以在 Eclipse 中,两个 for 循环花费相同的时间,但是从命令行运行时,原始版本比微优化版本花费的时间多 800%。差异之大让我大吃一惊。我猜 Eclipse 使用了不同的 JVM,它应用了一些智能优化技巧。

          但这并不意味着您应该开始使用微优化版本。在几乎所有情况下,您迭代的列表可能非常小,以至于性能差异可以忽略不计。使用标准版本获得的可读性(每个人都会更快地识别和理解)比不显着的性能提升更有益。

          为了完整起见,这是我运行的代码:

          public static void main(String[] args) {
                  List<Byte> list = initializeList();
                  byte value = 0;
                  final int NUM_LOOPS = 100;
          
                  long startOriginal, startOptimized, endOriginal, endOptimized;
          
                  startOptimized = System.currentTimeMillis();
                  for (int j = 0; j < NUM_LOOPS; j++) {
                      for (int i = -1, size = list.size(); ++i < size;) {
                          value = list.get(i);
                      }
                  }
                  endOptimized = System.currentTimeMillis();
          
                  startOriginal = System.currentTimeMillis();
                  for (int j = 0; j < NUM_LOOPS; j++) {
                      for (int i = 0; i < list.size(); i++) {
                          value = list.get(i);
                      }
                  }
                  endOriginal = System.currentTimeMillis();
          
                  System.out.println(value);
                  printResults(startOriginal, endOriginal, startOptimized, endOptimized);
              }
          
              private static void printResults(long startOriginal, long endOriginal,
                      long startOptimized, long endOptimized) {
          
                  long timeOriginal = endOriginal - startOriginal;
                  long timeOptimized = endOptimized - startOptimized;
          
                  long diff = Math.abs(timeOriginal - timeOptimized);
                  long min = Math.min(timeOriginal, timeOptimized);
          
                  System.out.println("Time for Original: " + timeOriginal + " ms"
                          + "   Time for MicroOptimized " + timeOptimized + " ms");
          
                  System.out.println("Fastest Loop: "
                          + ((timeOriginal < timeOptimized) ? "Original"
                                  : "MicroOptimized"));
          
                  System.out.println("Slowest loop takes " + ((double) 100 * diff / min)
                          + "% more time");       
              }
          
              public static List<Byte> initializeList(){
                  List<Byte> list = new ArrayList<Byte>();
                  final Byte ONE = new Byte((byte) 1);
          
                  for (int i = 0; i < Integer.MAX_VALUE / 10; i++) {
                      list.add(ONE);
                  }
          
                  return list;
              }
          }
          

          【讨论】:

            【解决方案9】:

            此外,如果您想知道将方法调用用作源集合是否会对性能产生任何影响。那就是 - 该方法是否会被多次调用 - 答案是否定的。这是一个例子:

            import java.util.*;
            public class TestForeach {
                public static void main (String[] args) {
            
                    for (String s : getStrings()) {
                        System.out.println("The string was: "+s);
                    }
                } 
            
                private static List<String> getStrings() {
                    System.out.println("IN GET STRINGS");
                    return Arrays.asList("A","B","C");
                }
            }
            

            这将导致:

            IN GET STRINGS
            The string was: A
            The string was: B
            The string was: C
            

            因此,该方法只会被调用一次。

            【讨论】:

              【解决方案10】:

              简单而有效

               for (ConfigDataModel.VisaTypesBean.AddedBean visatype : visaTypesBeans) {
                                          if (visatype.getId() == 24) {
              
                                          }
              

              【讨论】:

                【解决方案11】:

                任何一个都可以。根据 JVM,第二个可能会快几个时钟周期,但这将是不可估量或微不足道的差异。请注意这些类型的子优化。除非您正在构建一个实时系统,其中每个 CPU 滴答都很重要,否则它们只会增加复杂性和更多错误来源。

                我建议使用迭代器构造(正如已经建议的那样)

                for (Flower flower: flowers) { ...
                

                它清晰、灵活且可预测。

                【讨论】:

                  【解决方案12】:

                  我的方法在这个问题上有点不同。对我来说,你选择哪种方法并不重要。原因是您将在最佳优化方法中获得的“性能改进”将为 2,500,000 次迭代约 50 毫秒! (根据@David 的帖子)。显然,这种改进并不是您想浪费宝贵的时间来寻找优化的解决方案。

                  (但是,根据 OP 的原始问题,我还想建议最后一种方法。)

                  我知道答案有点奇怪和不常见,但这就是现实。

                  【讨论】:

                    【解决方案13】:

                    为了在编写代码时避免所有这些编号、迭代器和检查,请使用以下简单且可读性最强的代码,该代码具有最大的性能。为什么这具有最佳性能(详细信息即将发布)

                    for (Object object : aCollection) { 
                    // Do something here
                    }
                    

                    如果需要索引,则: 要在上述两种形式之间进行选择: 第二个更好,因为您使用局部变量进行检查。当方法退出时,变量会被放到堆栈的垃圾箱中。

                    【讨论】:

                      【解决方案14】:

                      只选择第一个,因为两者都相同,但在第二个代码 sn-p 中创建了一个多余的 int 变量。

                      所以去找第一个代码sn-p

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

                      【讨论】:

                      • 它们并不相同。第二个应该始终是首选。 >_
                      【解决方案15】:
                      String d = JOptionPane.showInputDialog("enter start");
                      int s = Integer.parseInt(d);
                      String h = JOptionPane.showInputDialog("enter the end");
                      int z = Integer.parseInt(h);
                      for (int a = 1 ; a<10 ; a++) { 
                          if (a%2 == 0 ) {
                              JOptionPane.showMessageDialog(null, a);
                              System.out.print(a);
                          }
                      }    
                      

                      【讨论】:

                      • 您可能想为您的答案添加一些解释。
                      猜你喜欢
                      • 1970-01-01
                      • 2015-04-15
                      • 1970-01-01
                      • 2020-11-07
                      • 2018-12-23
                      • 2016-02-23
                      • 2019-10-09
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多