gpt4 book ai didi

Java Enhanced-For-Loop 比传统更快?

转载 作者:行者123 更新时间:2023-12-04 01:45:23 30 4
gpt4 key购买 nike

所以我的理解是增强的 for 循环应该更慢,因为它们必须使用迭代器..但是我的代码提供了混合结果..(是的,我知道循环逻辑占用了循环中花费的大部分时间)

对于少量迭代(100-1000),增强的 for 循环在有和没有 JIT 的情况下似乎要快得多。相反,当迭代次数较多(100000000)时,传统循环要快得多。这里发生了什么?

public class NewMain {

public static void main(String[] args) {

System.out.println("Warming up");

int warmup = 1000000;
for (int i = 0; i < warmup; i++) {
runForLoop();
}
for (int i = 0; i < warmup; i++) {
runEnhancedFor();
}

System.out.println("Running");
int iterations = 100000000;
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
runForLoop();
}
System.out.println((System.nanoTime() - start) / iterations + "nS");

start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
runEnhancedFor();
}
System.out.println((System.nanoTime() - start) / iterations + "nS");
}

public static final List<Integer> array = new ArrayList(100);

public static int l;

public static void runForLoop() {
for (int i = 0; i < array.size(); i++) {
l += array.get(i);
}
}

public static void runEnhancedFor() {
for (int i : array) {
l += i;
}
}
}

最佳答案

错误的基准测试。错误的非详尽 list :

  • 没有适当的预热 :单次测量几乎总是错误的;
  • 在单一方法中混合多个代码路径 :我们可能开始使用仅适用于方法中第一个循环的执行数据来编译方法;
  • 来源可预测 :如果循环编译,我们实际上可以预测结果;
  • 结果是死代码消除 : 如果循环编译通过,我们可以丢弃循环

  • 花点时间听听 these talks ,并通过 these samples .

    这就是您使用 jmh 可以说是正确的方法:
    @OutputTimeUnit(TimeUnit.NANOSECONDS)
    @BenchmarkMode(Mode.AverageTime)
    @Warmup(iterations = 3, time = 1)
    @Measurement(iterations = 3, time = 1)
    @Fork(3)
    @State(Scope.Thread)
    public class EnhancedFor {

    private static final int SIZE = 100;

    private List<Integer> list;

    @Setup
    public void setup() {
    list = new ArrayList<Integer>(SIZE);
    }


    @GenerateMicroBenchmark
    public int enhanced() {
    int s = 0;
    for (int i : list) {
    s += i;
    }
    return s;
    }

    @GenerateMicroBenchmark
    public int indexed() {
    int s = 0;
    for (int i = 0; i < list.size(); i++) {
    s += list.get(i);
    }
    return s;
    }

    @GenerateMicroBenchmark
    public void enhanced_indi(BlackHole bh) {
    for (int i : list) {
    bh.consume(i);
    }
    }

    @GenerateMicroBenchmark
    public void indexed_indi(BlackHole bh) {
    for (int i = 0; i < list.size(); i++) {
    bh.consume(list.get(i));
    }
    }

    }

    ...产生的结果如下:
    Benchmark                         Mode   Samples      Mean   Mean error    Units
    o.s.EnhancedFor.enhanced avgt 9 8.162 0.057 ns/op
    o.s.EnhancedFor.enhanced_indi avgt 9 7.600 0.067 ns/op
    o.s.EnhancedFor.indexed avgt 9 2.226 0.091 ns/op
    o.s.EnhancedFor.indexed_indi avgt 9 2.116 0.064 ns/op

    现在这是增强循环和索引循环之间的微小差异,通过采用不同的代码路径来访问后备存储,可以天真地解释这种差异。然而,解释其实要简单得多: OP 忘记填写列表 ,这意味着循环体 永远不会被执行 ,而基准实际上是测量 size()的成本对比 iterator() !

    解决这个问题:
    @Setup
    public void setup() {
    list = new ArrayList<Integer>(SIZE);
    for (int c = 0; c < SIZE; c++) {
    list.add(c);
    }
    }

    ...然后产生:
    Benchmark                         Mode   Samples       Mean   Mean error    Units
    o.s.EnhancedFor.enhanced avgt 9 171.154 25.892 ns/op
    o.s.EnhancedFor.enhanced_indi avgt 9 384.192 6.856 ns/op
    o.s.EnhancedFor.indexed avgt 9 148.679 1.357 ns/op
    o.s.EnhancedFor.indexed_indi avgt 9 465.684 0.860 ns/op

    请注意,即使在纳米级,差异也非常小,并且非平凡的循环体将消耗差异,如果有的话。这里的差异可以通过我们内联的幸运程度来解释 get()Iterator方法,以及在这些内联之后我们可以享受的优化。

    请注意 indi_*测试,它否定循环展开优化。即使 indexed成功展开时性能更佳,但展开中断时则相反!

    像这样的标题, indexed的区别和 enhanced无非是学术兴趣。找出确切生成的代码 -XX:+PrintAssembly对于所有情况,都留给读者练习:)

    关于Java Enhanced-For-Loop 比传统更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20896432/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com