用于Java中的循环优化

时间:2016-09-01 07:08:34

标签: java for-loop

我今天在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循环吗?

9 个答案:

答案 0 :(得分:3)

没什么,它完全一样。除非您将element.length保存在变量中并使用该变量。他们在同一时间也这样做。最后一个例子是最好的,因为它是最可读的imho。

答案 1 :(得分:3)

优化的黄金法则是,不要。

第二个黄金法则是,不是。

第三条黄金法则实际上是一个问题,你有没有测量过?

回答您的问题,请使用您团队中接受的标准:除非真的不同,否则您的代码不应该有任何不同。也就是说,在一个优秀的团队中,代码是相似和无聊的。任何一步都是一个巨大的闪烁的标志,说:“小心,这是规则的例外!” - 除非你想准确地说出来,否则请与其他人一起使用。

现在,代码方面,答案在编译器中:

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,JIT或非JIT中的行为几乎相同。 b()略有不同:此处,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之后;回答for-each和包含的流)。当我们运行它时,

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和流媒体解决方案。正如你所看到的,for-each几乎相同(虽然更具可读性);流明显慢。我将离开javap examination这两个解决方案作为读者的练习。

答案 2 :(得分:2)

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

答案 3 :(得分:1)

我会这样做:

for(StackTraceElement element : elements) { 
     logger.log(Level.WARNING, element.getMethodName());
}

答案 4 :(得分:1)

您也可以按照以下方式编写代码。

StackTraceElement elements[] = e.getStackTrace();

for(StackTraceElement element : elements) {
    logger.log(Level.WARNING, element.getMethodName());
}

答案 5 :(得分:1)

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

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

答案 6 :(得分:1)

实际上,这两个选项之间存在细微差别:

n:在这里,您将重新计算elements.length final int n = elements.length; for (int i = 0; i < n; i++) 次。现在,大多数情况下,集合的大小都会被缓存,但有时会在每次调用方法时重新计算大小,从而增加了时间复杂度。

for each

在这里,您只计算一次长度并使用它。将它作为最终决定也很重要,因为你不希望任何人改变它。

但是,最好的方法是使用for (StackTraceElement element : e.getStackTrace())循环,如下所示:

p4 annotate - 这将消除计数器的使用,看起来更好,因此更具可读性,并可能有一些性能提升。

答案 7 :(得分:1)

没有区别。

<强>情况#1

您只是创建一个额外的变量并使用它。

<强>例#2

每次循环运行elements.length方法都将被计算。

<强>案#3

这与案例1一样好。

答案 8 :(得分:0)

在这个循环中:

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());
}

您在n变量中保存长度,但是对于每次迭代,您都可以访问n变量。

是一样的。

你可以写:

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

用于ArrayList的更易读的解决方案,例如:

arrayName.stream().forEach( item -> System.out.println(item.toString()));

此解决方案需要java 8。