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

时间:2014-01-03 04:13:53

标签: java performance loops for-loop

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

对于少量迭代(100-1000),使用和不使用JIT时,增强的for循环似乎要快得多。相反,迭代次数很多(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;
        }
    }
}

2 个答案:

答案 0 :(得分:31)

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

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

花时间聆听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 FORGOT要占据列表,这意味着循环体永远不会被执行,而基准实际上是衡量{的成本{1}} vs 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_*在成功展开时享有更好的性能,但在展开时却相反!

有了这样的标题,indexedindexed之间的区别仅仅是学术兴趣。确定所有案例的确切生成代码enhanced留给读者练习:)

答案 1 :(得分:2)

问题中存在两个截然不同的问题。一个是有效的观察结果,在一个特定程序中,具有低迭代计数,增强的循环时间更快。另一个是对该观察的概括为“对于少量迭代(100-1000),无论有没有JIT,增强的for循环似乎要快得多。”

我认为这种概括是没有道理的。我对程序做了一些小改动,首先运行基本的for循环测试,然后是增强的for循环。我还标记了输出,以减少处理修改版本时的混乱。这是我100次迭代的输出:

Warming up
Running
Enhanced For-Loop 2002nS
Basic For-Loop 70nS

按照原始顺序循环,我得到:

Warming up
Running
Basic For-Loop 2139nS
Enhanced For-Loop 137nS

如果我在运行之前立即预热第二个循环,而不是在开始时进行两个热身,我得到:

Warming up
Running
Basic For-Loop 1093nS
Enhanced For-Loop 984nS

对于低迭代次数,结果非常依赖于程序的精细细节,微基准测试的固有危险,以及避免从单个程序观察推广到关于测量代码将如何进行的一般假设的理由在任何其他程序中执行。