Java 8 初探 - Lazy Evaluation & Parallel Stream

更新於 發佈於 閱讀時間約 8 分鐘
此為過去的舊文,2014 年 4 月 20 日初次發表於 logdown。

除了 Pipe 的設計外,Stream 另外讓我好奇的二個特色:lazy evaluation parallel stream。lazy evaluation 可以想成是延後運算到真正必要的時候,而 parallel stream 則是將 Pipe 以平行運算的方式進行,最重要的是這二個特色都是針對 JVM 最佳化過的,應該比我們自己寫來的更有效率。

當看到 lazy evaluation 我最先想到的是,應該對載入大型檔案有幫助,例如:減少記憶體使用量,但我沒把握這想法是否正確,所以設計了一個實驗試試看我的想法。首先,設計一個 FileSearchStrategy 介面,可以輸入檔案(目錄)、關鍵字和結果收集器 (SearchResultCollector),每個實作可以用不同的方式從檔案中搜尋關鍵字,並將結果 <檔案名稱、行數、該行內容> 存放到收集器中。

由於 Java 的 File 可能指向一個檔案或目錄,因此 AbstractSearchStrategy 實作 FileSearchStrategysearch(File, String, SearchResultCollector),以遞迴的方式走訪每一層目錄,並留一個 hook method 讓繼承者提供實際掃描檔案的實作。

基礎架構完成後,第一個預設實作 DefaultSearchStrategy 是在還沒有 Stream API 之前,針對文字檔案常用的演算法:逐行掃描。這個實作的結果即是實驗的基準值。

接著 AllLinesSearchStrategy 的實作,使用在Java 7推出的 NIO 2 (New I/O 2) 套件所提供的 FilesreadAllLines(Path)函式,事實上,在 Java Doc 的說明中,這個函式只適合用在簡單的案例,並不適合用在大檔案上,因此,這個實作應該會得到實驗中最差的結果。

為了方便後續平行處理的實驗,StreamSearchStrategy 的實作,將實際 Pipe 的運算組成放到另一個函式:scanStream(Stream, String, SearchResultCollector)中,然後使用 BufferedReaderlines() 函式取得 Stream 物件進行運算。為了取得行號,Pipe 的第一個 intermediate operation 使用 map(Function),將字串轉成同時帶有行號與字串內容的物件 (使用 KeywordSearchResult 只是簡化實作),然後再用 filter(Predicate) 過濾掉不要的物件。

為了取得行號,所以 StreamSearchStrategyscanStream(Stream, String, SearchResultCollector) 中 Pipe 組成是先用 map(Function) 再用 filter(Predicate)。若忽略行號,改成 StreamSearchStrategyV2,先使用 filter(Predicate) 再使用 map(Function),對結果會有影響嗎?

目前系統記憶體越來越大,作業系統常會將檔案內容快取在系統記憶體中,當第二次讀取相同檔案時,速度可以加速許多,但對實驗來說,這會是個影響數據的關鍵,為了避免讀到快取的檔案內容,實驗準備了三個資料夾,每個資料夾放入 Table 1 所述相同的檔案結構,以 A 子資料夾為例,一個檔案有 100k 行 (筆) 資料,大小約 3.62 MB,這樣的檔案有 10 個檔案,B、C 等子資料夾依此類推,G 子資料夾則是將 A~F 子資料夾複製一份放入,120 個檔案合計 4.45 GB。


Table 1 - Test data

Table 1 - Test data


Table 2 - Test environment

Table 2 - Test environment


當然,這是否可行要看系統記憶體的多寡,實驗的環境如 Table 2 所列,而測試方法如下,每種 strategy 依序掃描三個資料夾,當第二個 strategy 開始掃描第一個資料夾時,因其他兩個資料夾在前一個 strategy 載入,總量有 8.9 GB,應該要超過系統記憶體,第一個資料夾內的內容應該已不在快取中。實驗使用一個 MemoryUsageMonitor 的物件,定期監控JVM的記憶體使用量,並記錄每次掃描的峰值。


好啦!該是公布測試結果 (Table 3) 的時候了,All Lines 果然如預期般使用最多的記憶體(超過1GB),所花費的時間也是最長的,多了 20 秒左右,但 Default、Stream 和 Stream v2 之間的差異不大,就執行時間上,三者的差距大約3秒,而記憶體的使用量 Stream v2 和 Default 幾乎是一樣,但 Stream 一開始的 map(Function) 似乎是致命傷。

Table 3 - Test result of four strategies

Table 3 - Test result of four strategies


先前 AbstractSearchStrategy 使用的是傳統 foreach 方式走訪所有的檔案,如果使用 Stream的parallel 會有幫助嗎?還是更糟?所以,將 AbstractSearchStrategysearch(File, String, SearchResultCollector) 改成如下,再次執行測試。

原先期望透過 parallel stream 的方式加快執行速度,但從 Table 4 看來,幾乎沒有加速,反而還帶來了反效果:記憶體使用量暴增。令人意外的是 Stream v2 的記憶體使用量比 Stream 還多,不知該怎麼解釋。


Table 4 - Test result of four strategies with parallel directories traversal

Table 4 - Test result of four strategies with parallel directories traversal


似乎只要和 I/O 扯上關係,即使用平行運算的方式也沒有減少太多的執行時間,有時候反而還更慢,那如果資料不在硬碟的檔案裡,都已經在記憶體中,那效果會如何?因此,設計了第三個實驗:100k ~ 6400k 筆資料存放在 ArrayList 中,然後根據參數使用 parallelStream()stream() 作為 scanStream(Stream, String, SearchResultCollector) 的輸入。

實驗結果列於 Table 5,100k 一欄,不論是 Stream 或 Stream v2 哪個先執行都會得到不理想的數據,可能是程式冷啟動所引起,所以忽略 100k 欄的數值。從 200k 開始,不論使用 Stream 或 Stream v2,都可看到當使用 parallelStream() 時,執行時間有明顯的減少,在 Stream v2 的 6400k 一欄,節省了 136 ms。而且整體來說,Stream v2 也明顯比 Stream 要好。


Table 5 - The execution time (ms) with parallel stream

Table 5 - The execution time (ms) with parallel stream


該是結論的時候了,首先,lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,I/O 類型或是存取上有競爭現象的資料較難發揮出效益,但若是在記憶體當中的資料,彼此無存取競爭 (不用使用 lock) 的現象,那 parallel stream 的效果就相當明顯,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。

留言
avatar-img
留言分享你的想法!
avatar-img
Spirit的沙龍
55會員
107內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
Spirit的沙龍的其他內容
2024/05/23
本書大多數的內容都以 OO 的概念出發,詳列了許多設計的臭味道,也有大量的例子。個人雖然不會這樣寫程式,但仍是覺得受益良多,至少在 code review 時能更清楚知道該怎麼描述問題。不過,即便不是用 OO 的概念,有些章節還是可以帶來一些想法,用 OO 概念寫程式的人更不該錯過這本好書。
Thumbnail
2024/05/23
本書大多數的內容都以 OO 的概念出發,詳列了許多設計的臭味道,也有大量的例子。個人雖然不會這樣寫程式,但仍是覺得受益良多,至少在 code review 時能更清楚知道該怎麼描述問題。不過,即便不是用 OO 的概念,有些章節還是可以帶來一些想法,用 OO 概念寫程式的人更不該錯過這本好書。
Thumbnail
2024/05/11
實際就業後,會發現收集與分析需求,通常都不是工程師在做,會有另一群人,以非工程的角度收集及分析需求,然後在開發過程中蹦出不同的火花,於是很好奇另一群人的想法是什麼?我不敢說這本書能完全代表另一群人的想法,但確實能夠得到很多有用的思維。推薦給所有的軟體工程師。
Thumbnail
2024/05/11
實際就業後,會發現收集與分析需求,通常都不是工程師在做,會有另一群人,以非工程的角度收集及分析需求,然後在開發過程中蹦出不同的火花,於是很好奇另一群人的想法是什麼?我不敢說這本書能完全代表另一群人的想法,但確實能夠得到很多有用的思維。推薦給所有的軟體工程師。
Thumbnail
2024/05/09
本書介紹了戰略設計、管理領域複雜度、實際應用領域驅動設計等主題。透過對核心子領域、支持子領域、限界上下文等概念的探討,提供了領域驅動設計的相關知識。這篇文章中還涉及了微服務、事件驅動架構和資料網格等相關主題,提供了設計系統和應用領域驅動設計的指導。
Thumbnail
2024/05/09
本書介紹了戰略設計、管理領域複雜度、實際應用領域驅動設計等主題。透過對核心子領域、支持子領域、限界上下文等概念的探討,提供了領域驅動設計的相關知識。這篇文章中還涉及了微服務、事件驅動架構和資料網格等相關主題,提供了設計系統和應用領域驅動設計的指導。
Thumbnail
看更多
你可能也想看
Thumbnail
常常被朋友問「哪裡買的?」嗎?透過蝦皮分潤計畫,把日常購物的分享多加一個步驟,就能轉換成現金回饋。門檻低、申請簡單,特別適合學生與上班族,讓零碎時間也能創造小確幸。
Thumbnail
常常被朋友問「哪裡買的?」嗎?透過蝦皮分潤計畫,把日常購物的分享多加一個步驟,就能轉換成現金回饋。門檻低、申請簡單,特別適合學生與上班族,讓零碎時間也能創造小確幸。
Thumbnail
嗨!歡迎來到 vocus vocus 方格子是台灣最大的內容創作與知識變現平台,並且計畫持續拓展東南亞等等國際市場。我們致力於打造讓創作者能夠自由發表、累積影響力並獲得實質收益的創作生態圈!「創作至上」是我們的核心價值,我們致力於透過平台功能與服務,賦予創作者更多的可能。 vocus 平台匯聚了
Thumbnail
嗨!歡迎來到 vocus vocus 方格子是台灣最大的內容創作與知識變現平台,並且計畫持續拓展東南亞等等國際市場。我們致力於打造讓創作者能夠自由發表、累積影響力並獲得實質收益的創作生態圈!「創作至上」是我們的核心價值,我們致力於透過平台功能與服務,賦予創作者更多的可能。 vocus 平台匯聚了
Thumbnail
本章節的目的是介紹Java中的套件使用,包括如何引用第三方套件和自定義模組,如何創建和使用自定義套件,以及介紹一些常見的Java標準庫套件。這些內容將幫助讀者更好地理解和使用Java的套件系統。
Thumbnail
本章節的目的是介紹Java中的套件使用,包括如何引用第三方套件和自定義模組,如何創建和使用自定義套件,以及介紹一些常見的Java標準庫套件。這些內容將幫助讀者更好地理解和使用Java的套件系統。
Thumbnail
此章節旨在介紹Java的基本語法、註解和變數的使用。透過學習,讀者將了解Java程式的基本結構、程式進入點的定義、如何撰寫單行和多行註解,以及如何宣告和初始化變數。
Thumbnail
此章節旨在介紹Java的基本語法、註解和變數的使用。透過學習,讀者將了解Java程式的基本結構、程式進入點的定義、如何撰寫單行和多行註解,以及如何宣告和初始化變數。
Thumbnail
這篇文章的目的是對Java程式設計語言進行介紹,包括它的特性、應用範疇、主要使用者,以及相關的學習資源和常見的庫與框架。此外,它也提供了一些學習Java的渠道,以及與Java相關的其他知識。
Thumbnail
這篇文章的目的是對Java程式設計語言進行介紹,包括它的特性、應用範疇、主要使用者,以及相關的學習資源和常見的庫與框架。此外,它也提供了一些學習Java的渠道,以及與Java相關的其他知識。
Thumbnail
這幾年新出的語言都強打在少寫 code 和提高可讀性,更重要的是能更容易發展出 domain specific language,就這一點 Java 確實有點顯得疲態了。其實文中列的特性大多是一些語法糖衣,但對程式的可讀性和抽象度都能提昇不少,我覺得挺實用也很划算的。
Thumbnail
這幾年新出的語言都強打在少寫 code 和提高可讀性,更重要的是能更容易發展出 domain specific language,就這一點 Java 確實有點顯得疲態了。其實文中列的特性大多是一些語法糖衣,但對程式的可讀性和抽象度都能提昇不少,我覺得挺實用也很划算的。
Thumbnail
default methods 似乎也引起不小的討論,因為 default methods 加上可以實作多個介面,已經有點像 C++ 的多重繼承了,只差在沒辦法繼承成員變數而已,是好是壞就看怎麼使用了。我個人覺得還蠻方便的
Thumbnail
default methods 似乎也引起不小的討論,因為 default methods 加上可以實作多個介面,已經有點像 C++ 的多重繼承了,只差在沒辦法繼承成員變數而已,是好是壞就看怎麼使用了。我個人覺得還蠻方便的
Thumbnail
Lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。
Thumbnail
Lazy evaluation 的效益必須是在 pipe 的組合上有最佳化過的,若組合的不好反而更糟糕,且在 I/O 上幫助似乎也不大。parallel stream 要能發揮效果必須看資料的來源類型,不過要注意的是 parallel stream 也會使記憶體的使用量增加,使用上也要小心。
Thumbnail
老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
Thumbnail
老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
Thumbnail
最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
Thumbnail
最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
Thumbnail
Kafka是一個先進的分佈式流處理平臺,具有高吞吐量、可擴展性、容錯性和低延遲特性,提供瞭解耦、非同步和削峰特點。本文介紹了Kafka的通訊模式、適合的應用場景和未來發展趨勢,旨在幫助使用者更好地理解和應用Kafka。
Thumbnail
Kafka是一個先進的分佈式流處理平臺,具有高吞吐量、可擴展性、容錯性和低延遲特性,提供瞭解耦、非同步和削峰特點。本文介紹了Kafka的通訊模式、適合的應用場景和未來發展趨勢,旨在幫助使用者更好地理解和應用Kafka。
Thumbnail
pipe 代表函數式程式設計中的概念,利用多個功能結合在一起,資料依序通過每個功能進行處理。文章中介紹了 pipe 的優點、兩個等效的程式碼比較以及 remeda 套件的使用。詳細介紹了使用 pipe 的好處,並提供了多個相關的例子,展示了 pipe 可讀性的提升。
Thumbnail
pipe 代表函數式程式設計中的概念,利用多個功能結合在一起,資料依序通過每個功能進行處理。文章中介紹了 pipe 的優點、兩個等效的程式碼比較以及 remeda 套件的使用。詳細介紹了使用 pipe 的好處,並提供了多個相關的例子,展示了 pipe 可讀性的提升。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News