Java8 的 Stream API 性能究竟如何?今天咱們來好好測驗一把

做者:Carpenter Lee
java

github.com/CarpenterLee/JavaLambdaInternalsgit

Stream Performance

已經對 Stream API 的用法鼓吹夠多了,用起簡潔直觀,但性能到底怎麼樣呢?會不會有很高的性能損失?本節咱們對 Stream API 的性能一探究竟。github

爲保證測試結果然實可信,咱們將 JVM 運行在 -server模式下,測試數據在 GB 量級,測試機器採用常見的商用服務器,配置以下:數組

OS CentOS 6.7 x86_64
CPU Intel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads
內存 96GB
JDK java version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM

測試方法和測試數據

性能測試並非容易的事,Java 性能測試更費勁,由於虛擬機對性能的影響很大,JVM 對性能的影響有兩方面:服務器

  1. GC 的影響。GC 的行爲是 Java 中很很差控制的一塊,爲增長肯定性,咱們手動指定使用 CMS 收集器,並使用 10GB 固定大小的堆內存。具體到 JVM 參數就是 -XX:+UseConcMarkSweepGC-Xms10G-Xmx10G微信

  2. JIT(Just-In-Time) 即時編譯技術。即時編譯技術會將熱點代碼在 JVM 運行的過程當中編譯成本地代碼,測試時咱們會先對程序預熱,觸發對測試函數的即時編譯。相關的 JVM 參數是 -XX:CompileThreshold=10000app

Stream 並行執行時用到 ForkJoinPool.commonPool()獲得的線程池,爲控制並行度咱們使用 Linux 的 taskset命令指定 JVM 可用的核數。函數

測試數據由程序隨機生成。爲防止一次測試帶來的抖動,測試 4 次求出平均時間做爲運行時間。性能

實驗一 基本類型迭代

測試內容:找出整型數組中的最小值。對比 for 循環外部迭代和 Stream API 內部迭代性能。學習

測試程序 IntTest,測試結果以下圖:

圖中展現的是 for 循環外部迭代耗時爲基準的時間比值。分析以下:

  1. 對於基本類型 Stream 串行迭代的性能開銷明顯高於外部迭代開銷(兩倍);

  2. Stream 並行迭代的性能比串行迭代和外部迭代都好。

並行迭代性能跟可利用的核數有關,上圖中的並行迭代使用了所有 12 個核,爲考察使用核數對性能的影響,咱們專門測試了不一樣核數下的 Stream 並行迭代效果:

分析,對於基本類型:

  1. 使用 Stream 並行 API 在單核狀況下性能不好,比 Stream 串行 API 的性能還差;

  2. 隨着使用核數的增長,Stream 並行效果逐漸變好,比使用 for 循環外部迭代的性能還好。

以上兩個測試說明,對於基本類型的簡單迭代,Stream 串行迭代性能更差,但多核狀況下 Stream 迭代時性能較好。

實驗二 對象迭代

再來看對象的迭代效果。

測試內容:找出字符串列表中最小的元素(天然順序),對比 for 循環外部迭代和 Stream API 內部迭代性能。

測試程序 StringTest,測試結果以下圖:

結果分析以下:

  1. 對於對象類型 Stream 串行迭代的性能開銷仍然高於外部迭代開銷(1.5 倍),但差距沒有基本類型那麼大。

  2. Stream 並行迭代的性能比串行迭代和外部迭代都好。

再來單獨考察 Stream 並行迭代效果:

分析,對於對象類型:

  1. 使用 Stream 並行 API 在單核狀況下性能比 for 循環外部迭代差;

  2. 隨着使用核數的增長,Stream 並行效果逐漸變好,多核帶來的效果明顯。

以上兩個測試說明,對於對象類型的簡單迭代,Stream 串行迭代性能更差,但多核狀況下 Stream 迭代時性能較好。

實驗三 複雜對象歸約

從實驗1、二的結果來看,Stream 串行執行的效果都比外部迭代差(不少),是否是說明 Stream 真的不行了?先別下結論,咱們再來考察一下更復雜的操做。

測試內容:給定訂單列表,統計每一個用戶的總交易額。對比使用外部迭代手動實現和 Stream API 之間的性能。

咱們將訂單簡化爲 <userName,price,timeStamp>構成的元組,並用 Order對象來表示。測試程序 ReductionTest,測試結果以下圖:

分析,對於複雜的歸約操做:

  1. Stream API 的性能廣泛好於外部手動迭代,並行 Stream 效果更佳;

再來考察並行度對並行效果的影響,測試結果以下:

分析,對於複雜的歸約操做:

  1. 使用 Stream 並行歸約在單核狀況下性能比串行歸約以及手動歸約都要差,簡單說就是最差的;

  2. 隨着使用核數的增長,Stream 並行效果逐漸變好,多核帶來的效果明顯。

以上兩個實驗說明,對於複雜的歸約操做,Stream 串行歸約效果好於手動歸約,在多核狀況下,並行歸約效果更佳。咱們有理由相信,對於其餘複雜的操做,Stream API 也能表現出類似的性能表現。

結論

上述三個實驗的結果能夠總結以下:

  1. 對於簡單操做,好比最簡單的遍歷,Stream 串行 API 性能明顯差於顯示迭代,但並行的 Stream API 可以發揮多核特性。

  2. 對於複雜操做,Stream 串行 API 性能能夠和手動實現的效果匹敵,在並行執行時 Stream API 效果遠超手動實現。

因此,若是出於性能考慮,1. 對於簡單操做推薦使用外部迭代手動實現,2. 對於複雜操做,推薦使用 Stream API, 3. 在多核狀況下,推薦使用並行 Stream API 來發揮多核優點,4. 單核狀況下不建議使用並行 Stream API。

若是出於代碼簡潔性考慮,使用 Stream API 可以寫出更短的代碼。即便是從性能方面說,儘量的使用 Stream API 也另一個優點,那就是隻要 Java Stream 類庫作了升級優化,代碼不用作任何修改就能享受到升級帶來的好處。



- 往期精彩 -

 
     
     
      
      
      
 
     

終於有人把 CountDownLatch,CyclicBarrier,Semaphore 說明白了!


記一次很是有意思的 SQL 優化經歷!


來了來了,10個免費後臺管理系統模板,接私活專用!


全乾貨技術公衆號Java學習指南

👇👇

長按上方二維碼關注公衆號

本文分享自微信公衆號 - Java學習指南(gh_85b94beaede2)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索