Java8 Stream效能如何及評測工具推薦
作為技術人員,學習新知識是基本功課。有些知識是不得不學,有些知識是學了之後如虎添翼,Java8的Stream就是兼具兩者的知識。不學看不懂,學了寫起程式碼來如虎添翼。
在上篇《Java8 Stream新特性詳解及實戰》中我們介紹了Java8 Stream的基本使用方法,嘗試一下是不是感覺很爽?當只用一行程式碼就搞定最終結果時,是不是再也不想用for迴圈一遍遍去迭代了。
同時,你是否又看到類似《Java8 Lambda表示式和流操作如何讓你的程式碼變慢5倍》這樣的文章,那麼今天就帶大家通過編寫測試程式來一探究竟,看看Stream的效能到底如何。同時,帶大家認識一個非常不錯的效能測試工具junitperf。
測試環境
先同步一下測試環境及工具資訊:
- JDK版本:1.8.0_151。
- 電腦配置:MacBook Pro i7,16G記憶體。
- Java測試工具:junitperf及Junit。
- IDE:intellij IDEA。
在測試的過程中電腦中還開了其他很多應用,但基本上都沒進行操作。
實驗一:基本型別迭代
基本測試方案,先初始化一個int陣列,5億個隨機數。然後從這個陣列中找到最小的一個數。
採用三個單元測試方法來對照參考:
- testIntFor:測試for迴圈執行時間;
- testIntStream:測試序列Stream執行時間;
- testIntParallelStream:測試並行Stream執行時間;
測試程式相關程式碼:
public class StreamTest { public static int[] arr; @BeforeAll public static void init() { arr = new int[500000000]; randomInt(arr); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testIntFor() { minIntFor(arr); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testIntParallelStream() { minIntParallelStream(arr); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testIntStream() { minIntStream(arr); } private int minIntStream(int[] arr) { return Arrays.stream(arr).min().getAsInt(); } private int minIntParallelStream(int[] arr) { return Arrays.stream(arr).parallel().min().getAsInt(); } private int minIntFor(int[] arr) { int min = Integer.MAX_VALUE; for (int anArr : arr) { if (anArr < min) { min = anArr; } } return min; } private static void randomInt(int[] arr) { Random r = new Random(); for (int i = 0; i < arr.length; i++) { arr[i] = r.nextInt(); } } }
基本操作流程:通過@BeforeAll註解的init方法對陣列進行隨機初始化,然後再統一執行上面三個測試方法。
在單元測試的方法上都有下面的註解:
@JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class})
該註釋為junitperf提供的註解,其中duration為持續執行這段程式碼的時間,單位毫秒;warmUp預熱時間,這裡預熱1秒;reporter輸出報表格式,這裡採用HTML展示,可以更直觀看到效果。
好上面的一切都準備好了,剩下的就是統一執行單元測試。執行結果如下三個圖。
針對基礎型別(int)操作,結果分析:
- 序列Stream的執行的確不如for迴圈效能高,耗時大概是for迴圈的2倍。
- 並行Stream的執行效能要優於for迴圈,耗時大概是for迴圈的一半。
- 這裡沒有用不同核數的機器測試,但並行Stream隨著伺服器核數的增加,必然更快。
實驗二:物件迭代
生成一個List列表,列表中隨機生成10000000個字串,然後分別通過不同的方式計算獲得最小的字串。
基本操作與實驗一相同,不再貼出程式碼,直接看測試的效果圖。
針對物件(String)操作,結果分析:
- Stream的效能與for迴圈已經相差不大了,耗時大概是for迴圈的1.25倍左右。
- 並行Stream執行的效能要優於for迴圈,而且比基礎型別的優勢更高,耗時已經低於for迴圈的一半。
- 針對不同伺服器核數,Stream效率同樣會更加高。
實驗三:複雜物件歸約
生成一個List列表,列表裡面存放著1百萬個User物件。每個物件中都包含使用者名稱和使用者某次運動的距離,同一使用者可在List裡包含多條運動記錄。現在通過不同的方式來統計使用者的總共運動了多遠距離。
基本測試思路一致,這裡只貼出基於Stream的演算法的程式碼,以便大家瞭解Stream的複雜物件歸約如何使用。
// 序列寫法
users.stream().collect(
Collectors.groupingBy(User::getUserName,
Collectors.summingDouble(User::getMeters)));
// 並行寫法
users.parallelStream().collect(
Collectors.groupingBy(User::getUserName,
Collectors.summingDouble(User::getMeters)));
下面看測試結果的資料:
複雜物件歸約操作,結果分析:
- 基於Stream的操作明顯都高於for迴圈的效率,而且並行的效果更加明顯。
- 同樣,隨著伺服器核數的增加,並行Stream的效率會更高。
最後推薦一下這款用起來還不錯的Java效能測試工具,GitHub地址:https://github.com/houbb/junitperf。 上面有詳細的使用說明。唯一缺少的就是資料預初始化的示例,而本篇文章的示例中已經補上了這部分缺失。
小結
通過上面的幾組實驗對比,我們可以看到如下結論:
- 針對簡單的操作,比如基礎型別的遍歷,使用for迴圈效能要明顯高於序列Stream操作。但Stream的並行操作隨著伺服器的核數增加,會優於for迴圈。
- 針對複雜操作,序列Stream效能與for迴圈不差上下,但並行Stream的效能已經是無法匹敵了。
- 特別是針對一個集合進行多層過濾並歸約操作,無論從寫法上或效能上都要明顯優於for迴圈。
用一句話來說就是:簡單操作for迴圈即可,複雜操作首推Stream。
現在的Stream書寫簡單,效能不錯,如果未來JDK針對其進行優化,便同時享受了便捷和效能,何樂而不為呢。
原文連結《Java8 Stream效能如何及評測工具推薦