2016-03-02 37 views
1

爲了檢查Java 8流和lambdas上的性能,我正在運行一些測試(非常基本,沒什麼奇怪的)。使用1000萬POJOS中的一個ArrayList,我所要做的就是獲得BigDecimal字段的平均值。爲了取得多於一個樣本,我運行了這個過程五次,令我吃驚的是這五次運行中的第一次比其他運行慢得多。我第一次獲得的值是0.38秒,其他四個則是0.04秒。這比快10倍!我也使用舊學校for(Pojo p : pojos)做了相同的測試,結果類似。爲什麼會發生這種情況,我該如何利用它?我正在使用的代碼是:Java 8,首次處理列表比後續處理慢

for (int i = 0; i < 5; i++) { 
    long init = System.nanoTime(); 
    BigDecimal sum = lista.parallelStream().map(x -> x.getCosto()).reduce(BigDecimal.ZERO, BigDecimal::add); 
    BigDecimal avg = sum.divide(BigDecimal.valueOf(registros)); 
    long end = System.nanoTime(); 
    System.out.println("End of processing: " + avg + " in " 
      + ((end - init)/1000000000.0) + " seconds."); 
} 
+3

不幸的是,進行性能測試並不容易,尤其是在與lambda表達式和方法引用結合使用時。你需要使用適當的工具,比如JMH框架。 – Tunaki

+1

我不同意重複標記。 OP詢問爲何第一次處理速度較慢。事實確實如此。即使OP使用JMH重寫了基準,第一次迭代將比後續迭代慢得多。考慮到我們講幾十毫秒,而不是微秒或納秒,OPs測量在方法上並不是那麼糟糕。 –

回答

4

有必要的,當你把它的第一次,包括以下步驟初始化流API恆定的延遲:

  • 加載很多幫手java.util.stream
  • java.lang.invoke包(如LambdaMetafactory)加載lambda生成類。
  • 爲涉及流管道(包括Stream API中內部使用的lambdas)生成lambda表達式和方法引用的運行時表示。
  • 所有這些字節碼的分層編譯(解釋器 - > C1 JIT - > C2 JIT)。只有在特定數量的方法調用(如5000)或特定數量的支持(循環迭代,如果該方法內部有大循環;如40000)後纔會觸發C2 JIT編譯(生成最快的代碼)。當大多數代碼不是C2編譯時,它的運行速度要慢得多。另外,JIT編譯器線程需要一些CPU時間,可用於實際計算。
  • 對於並行流:初始化公共ForkJoinPool,創建新線程。

所有這些步驟只執行一次。當您再次使用Stream API時,大部分工作已經完成,因此連續啓動速度更快。

在您的具體情況下,您正在密集使用堆,因此堆放大也可能是額外緩慢的原因。如果您的-Xms默認值太小,那麼垃圾收集器會執行幾個完整的gc循環,直到它將堆擴大到舒適的大小。您可以使用Xms == Xmx運行測試(例如-Xmx1G -Xms1G),這可能會提高第一次迭代速度。

+1

也許值得向「我該如何利用它?」的一般答案增加一些問題:通常,避免代碼重複,創建可重用類等。 – Holger

+0

謝謝你們兩位! – Emilio