有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

Java中的For循环优化

我今天在Java教程中阅读了以下代码

StackTraceElement elements[] = e.getStackTrace();
for (int i = 0, n = elements.length; i < n; i++) {
    logger.log(Level.WARNING, elements[i].getMethodName());
}

作为一名新手,我可能会编写如下代码

StackTraceElement elements[] = e.getStackTrace();

for (int i = 0; i < elements.length; i++) {
    logger.log(Level.WARNING, elements[i].getMethodName());
}

或者像下面这样

int n = elements.length;
for (int i = 0; i < n; i++) {
    logger.log(Level.WARNING, elements[i].getMethodName());
}

我只是想问他们之间有什么区别?还有其他更好的方法来优化Java中的for循环吗


共 (6) 个答案

  1. # 1 楼答案

    优化的黄金法则是,不要

    第二条金科玉律是,现在还没有

    第三条黄金法则实际上是一个问题,你衡量过了吗

    回答你的问题,遵循团队中公认的标准:你的代码不应该有任何不同,除非它做了一些真正不同的事情。也就是说,在一个好的团队中,代码都是相似的、乏味的。任何让开的地方都会有一个巨大的闪光标志,上面写着:“小心,这是规则的例外!”除非你真的想这么说,否则就随别人的便

    现在,就代码而言,答案在编译器中:

    import java.util.logging.*;
    
    public class ForLoop {
        private static Logger logger = Logger.getLogger("log");
    
        public static void a(Throwable e) {
            StackTraceElement elements[] = e.getStackTrace();
            for (int i = 0, n = elements.length; i < n; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    
        public static void b(Throwable e) {
            StackTraceElement elements[] = e.getStackTrace();
            for (int i = 0; i < elements.length; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    
        public static void c(Throwable e) {
            StackTraceElement elements[] = e.getStackTrace();
            int n = elements.length;
            for (int i = 0; i < n; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    }
    

    编译成

    alf-pro:Learning alf$ javap -p -c ForLoop.class 
      Compiled from "ForLoop.java"
      public class ForLoop {
        private static java.util.logging.Logger logger;
    
        public ForLoop();
          Code:
             0: aload_0       
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return        
    
        public static void a(java.lang.Throwable);
          Code:
             0: aload_0       
             1: invokevirtual #2                  // Method java/lang/Throwable.getStackTrace:()[Ljava/lang/StackTraceElement;
             4: astore_1      
             5: iconst_0      
             6: istore_2      
             7: aload_1       
             8: arraylength   
             9: istore_3      
            10: iload_2       
            11: iload_3       
            12: if_icmpge     36
            15: getstatic     #3                  // Field logger:Ljava/util/logging/Logger;
            18: getstatic     #4                  // Field java/util/logging/Level.WARNING:Ljava/util/logging/Level;
            21: aload_1       
            22: iload_2       
            23: aaload        
            24: invokevirtual #5                  // Method java/lang/StackTraceElement.getMethodName:()Ljava/lang/String;
            27: invokevirtual #6                  // Method java/util/logging/Logger.log:(Ljava/util/logging/Level;Ljava/lang/String;)V
            30: iinc          2, 1
            33: goto          10
            36: return        
    
        public static void b(java.lang.Throwable);
          Code:
             0: aload_0       
             1: invokevirtual #2                  // Method java/lang/Throwable.getStackTrace:()[Ljava/lang/StackTraceElement;
             4: astore_1      
             5: iconst_0      
             6: istore_2      
             7: iload_2       
             8: aload_1       
             9: arraylength   
            10: if_icmpge     34
            13: getstatic     #3                  // Field logger:Ljava/util/logging/Logger;
            16: getstatic     #4                  // Field java/util/logging/Level.WARNING:Ljava/util/logging/Level;
            19: aload_1       
            20: iload_2       
            21: aaload        
            22: invokevirtual #5                  // Method java/lang/StackTraceElement.getMethodName:()Ljava/lang/String;
            25: invokevirtual #6                  // Method java/util/logging/Logger.log:(Ljava/util/logging/Level;Ljava/lang/String;)V
            28: iinc          2, 1
            31: goto          7
            34: return        
    
        public static void c(java.lang.Throwable);
          Code:
             0: aload_0       
             1: invokevirtual #2                  // Method java/lang/Throwable.getStackTrace:()[Ljava/lang/StackTraceElement;
             4: astore_1      
             5: aload_1       
             6: arraylength   
             7: istore_2      
             8: iconst_0      
             9: istore_3      
            10: iload_3       
            11: iload_2       
            12: if_icmpge     36
            15: getstatic     #3                  // Field logger:Ljava/util/logging/Logger;
            18: getstatic     #4                  // Field java/util/logging/Level.WARNING:Ljava/util/logging/Level;
            21: aload_1       
            22: iload_3       
            23: aaload        
            24: invokevirtual #5                  // Method java/lang/StackTraceElement.getMethodName:()Ljava/lang/String;
            27: invokevirtual #6                  // Method java/util/logging/Logger.log:(Ljava/util/logging/Level;Ljava/lang/String;)V
            30: iinc          3, 1
            33: goto          10
            36: return        
    
        static {};
          Code:
             0: ldc           #7                  // String log
             2: invokestatic  #8                  // Method java/util/logging/Logger.getLogger:(Ljava/lang/String;)Ljava/util/logging/Logger;
             5: putstatic     #3                  // Field logger:Ljava/util/logging/Logger;
             8: return        
      }
    

    正如您所看到的,a()c()只在variable assignment中不同,因此在JVM中的行为几乎相同,无论是否为JITtedb()略有不同:这里,在循环中调用arraylength。正如@thurstycrow所指出的,这不一定是性能问题,但可能是

    所以,当谈到现实生活中的表现时,你需要确保它值得考虑。检查堆栈跟踪的代码几乎永远不值得优化,,因为你不需要一次又一次地获取堆栈跟踪。此外,我认为日志记录比数组长度检查要昂贵得多

    但是,如果我们假设您需要优化这段特定代码,比如说您正在编写一个定制的监视工具来检查抛出的所有异常,您希望它非常慢,而不是非常慢(您知道,收集堆栈跟踪非常昂贵;导航它通常非常便宜),您需要在野外测量它,因为JIT可以并且将考虑这段代码周围发生的一切

    但这是一个无聊的答案,所以尽管我们知道这没有多大意义,但我们还是要做一些基准测试——毕竟,我们可以,为什么不呢?你可以使用的一个很好的基准测试工具是JMH;你可以按照教程去玩它,或者直接抓住这个答案

    基准是

    package org.sample;
    
    import java.util.logging.*;
    import java.util.*;
    import org.openjdk.jmh.annotations.Benchmark;
    import org.openjdk.jmh.annotations.State;
    import org.openjdk.jmh.annotations.Scope;
    
    @State(Scope.Thread)
    public class ForLoop {
        private static Logger logger = Logger.getLogger("log");
        static {
            logger.setLevel(Level.OFF);
        }
    
        private StackTraceElement elements[] = new Exception().getStackTrace();
    
        @Benchmark
        public void a0() {
            for (int i = 0, n = elements.length; i < n; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    
        @Benchmark
        public void a() {
            for (int i = 0, n = elements.length; i < n; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    
        @Benchmark
        public void b() {
            for (int i = 0; i < elements.length; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    
        @Benchmark
        public void c() {
            int n = elements.length;
            for (int i = 0; i < n; i++) {
                logger.log(Level.WARNING, elements[i].getMethodName());
            }
        }
    
        @Benchmark
        public void d() {
            for (StackTraceElement e: elements) {
                logger.log(Level.WARNING, e.getMethodName());
            }
        }
    
        @Benchmark
        public void e() {
            Arrays.stream(elements)
                .forEach(item -> logger.log(Level.WARNING, item.getMethodName()));
        }
    }
    

    (稍后有更多关于a0的信息;包括每个和流的答案)。当我们运行它时

    alf-pro:test alf$ java -jar target/benchmarks.jar  -wi 5 -i 5 -t 4 -f 1
    # JMH 1.13 (released 41 days ago)
    # VM version: JDK 1.8.0, VM 25.0-b70
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
    # VM options: <none>
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 4 threads, will synchronize iterations
    # Benchmark mode: Throughput, ops/time
    # Benchmark: org.sample.ForLoop.a
    
    # Run progress: 0.00% complete, ETA 00:01:00
    # Fork: 1 of 1
    # Warmup Iteration   1: 139092694.098 ops/s
    # Warmup Iteration   2: 130833602.261 ops/s
    # Warmup Iteration   3: 121355640.666 ops/s
    # Warmup Iteration   4: 114037414.954 ops/s
    # Warmup Iteration   5: 110355992.809 ops/s
    Iteration   1: 111331613.966 ops/s
    Iteration   2: 111401698.736 ops/s
    Iteration   3: 108193438.523 ops/s
    Iteration   4: 105277721.260 ops/s
    Iteration   5: 103549199.780 ops/s
    
    
    Result "a":
      107950734.453 ±(99.9%) 13602765.938 ops/s [Average]
      (min, avg, max) = (103549199.780, 107950734.453, 111401698.736), stdev = 3532595.117
      CI (99.9%): [94347968.515, 121553500.391] (assumes normal distribution)
    
    
    # JMH 1.13 (released 41 days ago)
    # VM version: JDK 1.8.0, VM 25.0-b70
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
    # VM options: <none>
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 4 threads, will synchronize iterations
    # Benchmark mode: Throughput, ops/time
    # Benchmark: org.sample.ForLoop.a0
    
    # Run progress: 16.67% complete, ETA 00:00:51
    # Fork: 1 of 1
    # Warmup Iteration   1: 96143259.419 ops/s
    # Warmup Iteration   2: 107561397.775 ops/s
    # Warmup Iteration   3: 97488364.065 ops/s
    # Warmup Iteration   4: 95880266.969 ops/s
    # Warmup Iteration   5: 96943938.140 ops/s
    Iteration   1: 95208831.922 ops/s
    Iteration   2: 94012219.834 ops/s
    Iteration   3: 95369199.325 ops/s
    Iteration   4: 96090174.793 ops/s
    Iteration   5: 90375159.036 ops/s
    
    
    Result "a0":
      94211116.982 ±(99.9%) 8743077.939 ops/s [Average]
      (min, avg, max) = (90375159.036, 94211116.982, 96090174.793), stdev = 2270549.576
      CI (99.9%): [85468039.043, 102954194.921] (assumes normal distribution)
    
    
    # JMH 1.13 (released 41 days ago)
    # VM version: JDK 1.8.0, VM 25.0-b70
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
    # VM options: <none>
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 4 threads, will synchronize iterations
    # Benchmark mode: Throughput, ops/time
    # Benchmark: org.sample.ForLoop.b
    
    # Run progress: 33.33% complete, ETA 00:00:41
    # Fork: 1 of 1
    # Warmup Iteration   1: 58778298.331 ops/s
    # Warmup Iteration   2: 66786552.916 ops/s
    # Warmup Iteration   3: 67834850.418 ops/s
    # Warmup Iteration   4: 69421299.220 ops/s
    # Warmup Iteration   5: 72392517.533 ops/s
    Iteration   1: 70791625.464 ops/s
    Iteration   2: 70393808.080 ops/s
    Iteration   3: 68931230.026 ops/s
    Iteration   4: 67113234.799 ops/s
    Iteration   5: 71628641.383 ops/s
    
    
    Result "b":
      69771707.950 ±(99.9%) 6847578.759 ops/s [Average]
      (min, avg, max) = (67113234.799, 69771707.950, 71628641.383), stdev = 1778294.458
      CI (99.9%): [62924129.192, 76619286.709] (assumes normal distribution)
    
    
    # JMH 1.13 (released 41 days ago)
    # VM version: JDK 1.8.0, VM 25.0-b70
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
    # VM options: <none>
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 4 threads, will synchronize iterations
    # Benchmark mode: Throughput, ops/time
    # Benchmark: org.sample.ForLoop.c
    
    # Run progress: 50.00% complete, ETA 00:00:31
    # Fork: 1 of 1
    # Warmup Iteration   1: 87120233.617 ops/s
    # Warmup Iteration   2: 88506588.802 ops/s
    # Warmup Iteration   3: 82506886.728 ops/s
    # Warmup Iteration   4: 75379852.092 ops/s
    # Warmup Iteration   5: 83735974.951 ops/s
    Iteration   1: 80107472.633 ops/s
    Iteration   2: 85088925.886 ops/s
    Iteration   3: 81051339.754 ops/s
    Iteration   4: 85997882.597 ops/s
    Iteration   5: 87092494.956 ops/s
    
    
    Result "c":
      83867623.165 ±(99.9%) 11946241.585 ops/s [Average]
      (min, avg, max) = (80107472.633, 83867623.165, 87092494.956), stdev = 3102401.003
      CI (99.9%): [71921381.580, 95813864.750] (assumes normal distribution)
    
    
    # JMH 1.13 (released 41 days ago)
    # VM version: JDK 1.8.0, VM 25.0-b70
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
    # VM options: <none>
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 4 threads, will synchronize iterations
    # Benchmark mode: Throughput, ops/time
    # Benchmark: org.sample.ForLoop.d
    
    # Run progress: 66.67% complete, ETA 00:00:20
    # Fork: 1 of 1
    # Warmup Iteration   1: 90916967.864 ops/s
    # Warmup Iteration   2: 91629586.405 ops/s
    # Warmup Iteration   3: 107866944.554 ops/s
    # Warmup Iteration   4: 102023453.435 ops/s
    # Warmup Iteration   5: 111336773.130 ops/s
    Iteration   1: 107770637.269 ops/s
    Iteration   2: 107103283.175 ops/s
    Iteration   3: 107545862.850 ops/s
    Iteration   4: 113804266.277 ops/s
    Iteration   5: 114628060.965 ops/s
    
    
    Result "d":
      110170422.107 ±(99.9%) 14295432.924 ops/s [Average]
      (min, avg, max) = (107103283.175, 110170422.107, 114628060.965), stdev = 3712478.533
      CI (99.9%): [95874989.183, 124465855.031] (assumes normal distribution)
    
    
    # JMH 1.13 (released 41 days ago)
    # VM version: JDK 1.8.0, VM 25.0-b70
    # VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/jre/bin/java
    # VM options: <none>
    # Warmup: 5 iterations, 1 s each
    # Measurement: 5 iterations, 1 s each
    # Timeout: 10 min per iteration
    # Threads: 4 threads, will synchronize iterations
    # Benchmark mode: Throughput, ops/time
    # Benchmark: org.sample.ForLoop.e
    
    # Run progress: 83.33% complete, ETA 00:00:10
    # Fork: 1 of 1
    # Warmup Iteration   1: 33385239.816 ops/s
    # Warmup Iteration   2: 38698292.771 ops/s
    # Warmup Iteration   3: 38876327.513 ops/s
    # Warmup Iteration   4: 37957299.742 ops/s
    # Warmup Iteration   5: 39190117.656 ops/s
    Iteration   1: 38286235.065 ops/s
    Iteration   2: 39449652.416 ops/s
    Iteration   3: 38541883.894 ops/s
    Iteration   4: 40384962.473 ops/s
    Iteration   5: 36975457.540 ops/s
    
    
    Result "e":
      38727638.278 ±(99.9%) 4934050.936 ops/s [Average]
      (min, avg, max) = (36975457.540, 38727638.278, 40384962.473), stdev = 1281357.359
      CI (99.9%): [33793587.342, 43661689.213] (assumes normal distribution)
    
    
    # Run complete. Total time: 00:01:02
    
    Benchmark    Mode  Cnt          Score          Error  Units
    ForLoop.a   thrpt    5  107950734.453 ± 13602765.938  ops/s
    ForLoop.a0  thrpt    5   94211116.982 ±  8743077.939  ops/s
    ForLoop.b   thrpt    5   69771707.950 ±  6847578.759  ops/s
    ForLoop.c   thrpt    5   83867623.165 ± 11946241.585  ops/s
    ForLoop.d   thrpt    5  110170422.107 ± 14295432.924  ops/s
    ForLoop.e   thrpt    5   38727638.278 ±  4934050.936  ops/s
    

    我们可以看到,这三种方法都是如此之快,以至于同一种方法在运行几次时会产生非常不同的测量结果。对于这个例子,我就到此为止。对于您的实际情况,请随意使用产生问题的实际代码

    PS.添加了for each和流媒体解决方案。正如你所看到的,每一个都差不多(虽然可读性更强);溪流明显变慢了。我将把这两种解决方案的^{} examination留给读者作为练习

  2. # 2 楼答案

    我会这样做:

    for(StackTraceElement element : elements) { 
         logger.log(Level.WARNING, element.getMethodName());
    }
    
  3. # 3 楼答案

    实际上,最好不要将数组长度存储到变量中,因为每次迭代都必须进行额外的数组边界检查。看看this video(从28:00开始)。它详细描述了JIT编译器为优化代码所做的努力

  4. # 4 楼答案

    没什么,完全一样。除了将element.length保存在变量中并使用该变量。他们在同一时间做同样的事。最后一个例子是最好的,因为它是最容易理解的imho

  5. # 5 楼答案

    与第一个版本不同的是,代码每次都必须取消对elements的引用。从理论上讲,将值存储在变量中应该更有效,但这种操作非常便宜,在实践中几乎不会注意到差异

    与第二个版本的区别主要在于,在for循环的范围之外引入了变量n。也就是说,如果您想将n重新用作下面的变量名,则只能在其类型相同(在本例中为int)的情况下才能这样做

  6. # 6 楼答案

    您也可以按如下方式编写,以简化代码

    StackTraceElement elements[] = e.getStackTrace();
    
    for(StackTraceElement element : elements) {
        logger.log(Level.WARNING, element.getMethodName());
    }