Effective Java Item 48:使用 parallel streams 要非常小心
整理 Effective Java 書中 Item 48: Use streams judiciously 心得筆記
主旨:parallel stream 用錯,效能與正確性可能雙雙出問題
Java 8 的 stream API 帶來了非常方便的資料處理方式,加上 .parallel()
方法,更是讓你只用一行就能平行運算聽起來很香。
但實際上呢?用錯地方,不僅效能沒提升,甚至會程式掛掉。平行化是進階優化技巧,不是萬靈丹。
點出問題:為什麼不要亂用 parallel?
來看個例子,這是從前面出現過的 Mersenne Prime 計算程式:
如果加上 .parallel()
:
結果程式不會印東西,CPU 飆到 90% 卡在那裡。完全沒有加速,反而變成效能問題。
問題出在哪?
- 使用
Stream.iterate
來源:不易切割成子任務 - 使用
limit
:不確定要處理幾個元素,平行策略亂掉 - 產出成本極不平均(找下一個質數會越來越慢):平行處理反而更糟
劃重點:何時可以平行?何時絕對不要?
✅ 適合平行的場景
條件 | 說明 |
---|---|
資料結構可切割 | ArrayList、HashSet、ConcurrentHashMap、陣列、int/long 範圍 |
有 locality-of-reference | 資料連續性高,例如陣列 |
Terminal operation 是 reduction | 如:sum() 、count() 、reduce() |
資料量夠大 | 元素數量 × 每個元素處理行數 ≧ 100,000 |
無副作用、無共享狀態 | lambda 沒有寫入外部變數,也不會互相干擾 |
❌ 不建議平行的場景
- Stream 來源是
Stream.iterate()
或limit()
存在 - 用
forEach()
做計算邏輯(副作用過多) - Stream 中某些步驟非常慢或不可預測
- 小資料量卻硬平行,反而慢
- 不確定是否 thread-safe 的 function objects(容易出錯)
範例:有效加速的 parallel stream
來看看一個平行化很有效的例子,計算小於等於 n 的質數數量:
在 4 核心機器上:
- 單執行緒版本:31 秒
- 平行版本:9.2 秒(快了 3.7 倍)
這樣的工作符合所有條件:來源可切割、步驟類似、資料量大、終端是 count()
。
真實世界範例:資料分析任務
在資料工程裡常見的 ETL 任務,例如:
這類工作:
- 資料結構是 List(可切割)
- 每筆處理時間短且均勻
- 是計數任務 => 平行化非常合適。
小結:確認有幫助、再 parallel
parallel stream 用錯的代價很高,快不成反而掛掉。
使用前請確認:
- 正確性不會壞(沒有共享狀態與副作用)
- 有明確效能提升(測試前後比較)
- 使用資料來源與操作都是適合平行的結構與步驟
建議流程:
先寫正確的 sequential stream → 分析是否值得平行化 → 測試效能 → 確認再用 parallel()
平行化是種最佳化技巧,不是一定要的。
Read other posts