2014-10-28 26 views
12

IDEA建议更换,例如,这样的:IntelliJ IDEA建议用foreach方法替换循环。我是否应该尽可能地做到这一点?

for (Point2D vertex : graph.vertexSet()) { 
    union.addVertex(vertex); 
} 

与此:

graph.vertexSet().forEach(union::addVertex); 

这个新版本是肯定更具有可读性。但是在那种情况下,我最好坚持使用迭代表的旧语言结构,而不是使用新的方法?例如,如果我理解正确,方法引用机制意味着构造一个匿名Consumer对象,否则(与for语言构造)将不会构造。这会成为某些行为的性能瓶颈吗?

所以我写这个不是很详尽的基准:

package org.sample; 

import org.openjdk.jmh.annotations.Benchmark; 
import org.openjdk.jmh.annotations.Fork; 
import org.openjdk.jmh.annotations.Threads; 
import org.openjdk.jmh.infra.Blackhole; 
import org.tendiwa.geometry.Point2D; 

import java.util.ArrayList; 
import java.util.List; 
import java.util.stream.Collectors; 
import java.util.stream.IntStream; 

public class LanguageConstructVsForeach { 
    private static final int NUMBER_OF_POINTS = 10000; 
    private static final List<Point2D> points = IntStream 
     .range(0, NUMBER_OF_POINTS) 
     .mapToObj(i -> new Point2D(i, i * 2)) 
     .collect(Collectors.toList()); 

    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public void languageConstructToBlackhole(Blackhole bh) { 
     for (Point2D point : points) { 
      bh.consume(point); 
     } 
    } 
    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public void foreachToBlackhole(Blackhole bh) { 
     points.forEach(bh::consume); 
    } 
    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public List<Point2D> languageConstructToList(Blackhole bh) { 
     List<Point2D> list = new ArrayList<>(NUMBER_OF_POINTS); 
     for (Point2D point : points) { 
      list.add(point); 
     } 
     return list; 
    } 
    @Benchmark 
    @Threads(1) 
    @Fork(3) 
    public List<Point2D> foreachToList(Blackhole bh) { 
     List<Point2D> list = new ArrayList<>(NUMBER_OF_POINTS); 
     points.forEach(list::add); 
     return list; 
    } 

} 

,并得到:

Benchmark              Mode Samples  Score  Error Units 
o.s.LanguageConstructVsForeach.foreachToBlackhole    thrpt  60 33693.834 ± 894.138 ops/s 
o.s.LanguageConstructVsForeach.foreachToList     thrpt  60 7753.941 ± 239.081 ops/s 
o.s.LanguageConstructVsForeach.languageConstructToBlackhole thrpt  60 16043.548 ± 644.432 ops/s 
o.s.LanguageConstructVsForeach.languageConstructToList   thrpt  60 6499.527 ± 202.589 ops/s 

如何来foreach是在两种情况下更有效:当我这样做几乎没有,当我做一些实际工作? foreach简单封装Iterator?这个基准是否正确?如果是这样,今天有没有理由在Java 8中使用旧的语言结构?

+2

相关的问题:http://stackoverflow.com/q/23265855/1441122和http://stackoverflow.com/q/16635398/1441122 – 2014-10-28 19:12:37

+0

微基准只有他们的创造者在创造他们的专业知识一样准确和有用。 microbenchmarks通常直接发挥邓宁克鲁格效应与预期的结果。 – 2014-10-28 19:13:54

+1

@JarrodRoberson但是这个使用JMH,它有一个完善的方法和工具benchamrking。这个基准没有任何东西来自我的专业知识,而不是从手册到JMH。 – gvlasov 2014-10-28 19:17:46

回答

15

您正在比较语言的“增强型”循环与Iterable.forEach()方法。基准并没有明显的错误,并且结果可能会令人惊讶,直到您深入实施。

请注意,points列表是ArrayList的一个实例,因为这是Collectors.toList()收集器创建的。

Iterable上的增强型for循环从中获取Iterator,然后重复调用hasNext()next(),直到没有更多元素。 (这与循环数组中的增强型for循环不同,它执行算术和直接数组元素访问。)因此,循环遍历Iterable时,此循环每次迭代至少执行两次方法调用。

相比之下,调用ArrayList.forEach()在包含列表元素的数组上运行传统的基于int的for循环,并在每次迭代中调用一次lambda表达式。这里每次迭代只有一次调用,而不是每次迭代调用for-for循环的两次调用。这可以解释为什么ArrayList.forEach()在这种情况下更快。

除了运行循环之外,黑洞案例似乎只做很少的工作,所以这些案例似乎是测量纯循环开销。这可能是为什么ArrayList.forEach()在这里显示出如此巨大的优势。

当循环完成一小部分工作(添加到目的地列表)时,ArrayList.forEach()仍然具有速度优势,但它的差异要小得多。我怀疑如果你要在循环中做更多的工作,优势会更小。这表明任一构造的环路开销都很小。尝试在循环中使用BlackHole.consumeCPU()。如果两个结构之间的结果不可区分,我不会感到惊讶。

请注意,由于Iterable.forEach()最终在ArrayList.forEach()中有专门的实现,所以出现了很大的速度优势。如果您要在不同的数据结构上运行forEach(),则可能会得到不同的结果。

我不会使用这个作为理由来盲目地用Iterable.forEach()调用所有enhanced-for循环。编写最清晰的代码,这是最有意义的。如果您正在编写性能关键代码,请进行基准测试!根据工作负载,正在访问的数据结构等,不同的表单将具有不同的性能。

2

使用旧样式的一个明显原因是您将与Java 7兼容。如果您的代码使用大量新颖Java 8的好东西不是一种选择,但如果您只使用一些新功能,这可能是一个优势,特别是如果您的代码位于通用库中。

相关问题