2014-10-28 24 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的好東西不是一種選擇,但如果您只使用一些新功能,這可能是一個優勢,特別是如果您的代碼位於通用庫中。

相關問題