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 也會使記憶體的使用量增加,使用上也要小心。

    52會員
    102Content count
    這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
    留言0
    查看全部
    發表第一個留言支持創作者!
    Spirit的沙龍 的其他內容
    老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
    最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
    Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
    內容十分精實,一百多頁很薄的一本書,但含了很多有用的資訊,就算不是開發微服務,書中的內容也可以用在很多雲端服務的開發與維運上。中文版唯一可惜的地方,翻譯非常不通順,很多不像中文的句子,會看到好幾個「與」連在一起用,標點符號的用法也有點怪,閱讀的痛苦指數有點高...
    3/5高品質微服務
    今天的任務是找到獨一無案的方法創造新事物,不只讓未來變得不一樣,而且要更好,所以我們要從 0 到 1。最重要的第一步是自己獨立思考。唯有重新看待世界,像古人首次見到它那樣覺得新鮮古怪,我們才能重新創造,並將更好的未來留給後世。
    5/5從 0 到 1
    這陣子比較有空可以去天瓏書局晃晃,正好看到這本剛上市不久的書,整體上大多數守則,也是我自己一直在遵循的,是相當不錯的一本總結書。但真的要仔細看每一節的內容,理解每個原則背後的情境與想要改善的問題是什麼。如果只是把每一節的標題拿來使用,很容易就會發現衝突的部分。
    5/5程式設計守則
    老實說,看到 Java Sream API 讓我感到相當親切,這應該跟我研究所多年的研究題目是 visual dataflow language 有關,Java Stream API 把迴圈給內化了,每個 operation 的重點是要做什麼,大大提高了程式的抽象化程度和可讀性。
    最後,Java 8 雖然支援 Lambda,但我覺得 Closure 某種程度上還不稱不上是 Java 的第一級居民,我還是比較喜歡寫一些小而易測的 class,而不是使用 Lambda,至於捕捉變數,透過建構子將變數帶入物件也是一種方式。
    Java 8 終於在 2014 的 3 月 18 日正式釋出了,不過自從用 Objective C 開發 iOS App後,我已經有好一陣子沒碰 Java,期間曾經有短暫寫一點點,但卻沒有時間去用 beta 版的 Java 8,直到最近才又開始玩一下。
    內容十分精實,一百多頁很薄的一本書,但含了很多有用的資訊,就算不是開發微服務,書中的內容也可以用在很多雲端服務的開發與維運上。中文版唯一可惜的地方,翻譯非常不通順,很多不像中文的句子,會看到好幾個「與」連在一起用,標點符號的用法也有點怪,閱讀的痛苦指數有點高...
    3/5高品質微服務
    今天的任務是找到獨一無案的方法創造新事物,不只讓未來變得不一樣,而且要更好,所以我們要從 0 到 1。最重要的第一步是自己獨立思考。唯有重新看待世界,像古人首次見到它那樣覺得新鮮古怪,我們才能重新創造,並將更好的未來留給後世。
    5/5從 0 到 1
    這陣子比較有空可以去天瓏書局晃晃,正好看到這本剛上市不久的書,整體上大多數守則,也是我自己一直在遵循的,是相當不錯的一本總結書。但真的要仔細看每一節的內容,理解每個原則背後的情境與想要改善的問題是什麼。如果只是把每一節的標題拿來使用,很容易就會發現衝突的部分。
    5/5程式設計守則
    你可能也想看
    Thumbnail
    1.加權指數與櫃買指數 週五的加權指數在非農就業數據開出來後,雖稍微低於預期,但指數仍向上噴出,在美股開盤後於21500形成一個爆量假突破後急轉直下,就一路收至最低。 台股方面走勢需觀察週一在斷頭潮出現後,週二或週三開始有無買單進場支撐,在沒有明確的反轉訊號形成前,小夥伴盡量不要貿然抄底,或是追空
    Thumbnail
    重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
    Thumbnail
    近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
    Thumbnail
    相信很多初學者學python的原因,不外乎語法簡單、好上手、重點是有很多現成的套件可以玩。那麼,Java呢?有!當然有!而且還多到你不知道該選哪個好! 今天的文章主要示範如何在vscode新建立Java 的maven專案,並且透過maven安裝這些額外的套件(依賴)
    Thumbnail
    最近配合公司政策換了新電腦,重新回想起從頭建環境的惡夢。本篇文就來記錄一下如何開始踏入Java的第一步,方便起見也使用相對Eclipse、IntelliJ來說輕量不少的VScode作為編輯器。
    來到學期2-3的階段,第一個作業就是打造餐廳清單。原本認為經過電影清單的學習經歷之後,對於打造餐廳清單應該也不會太過困難;沒想到我花了2個月的時間才把作業完整交出去。 在寫餐廳清單的初期,第一個碰到的問題就是首頁無法秀出餐廳評分這個選項。我試著參考其他同學的作品也改了版面的設計,卻始終無法出現餐廳評
    Thumbnail
    位在通化街尾端,靠近六張犁的禾雀咖啡工作室,質樸的感覺是一家適合假日早晨來待上好一會兒的好去處。 主打自家烘培手沖及義式咖啡,最大特色還有自家製的麵包、甜點輕食,最喜歡吃的莫過於他們的鮮奶吐司。曾經住在這裡好長一段時間,週末去搶個一包可以在家裡烤來吃,是相當幸福的感覺。 冰羅馬咖啡則是我喝過最喜歡的
    Thumbnail
    abstract class = 抽象類別 interface = 介面 抽象類別與介面都無法建立物件。 1. 使用abstract關鍵字來建立抽象類別,interface關鍵字建立介面。 interface只能繼承interface,且可以繼承多個:
    Thumbnail
    繼承 在Java中,一個類可以由其他類派生。如果你要創建一個類,而且已經存在一個類具有你所需要的屬性或方法,那麼你可以將新創建的類繼承該類。 利用繼承的方法,可以重用已存在類的方法和屬性,而不用重寫這些代碼。被繼承的類稱為超類(super class),派生類稱為子類(subclass)。 繼承的特
    Thumbnail
    古早的年代想在網頁內埋 Java 還有 Java applet 可以用,在 Java applet 式微後,找來找去比較可以的辦法大概就是編譯成 WebAssembly 了吧! 想要把 Java 編譯成 WebAssembly,有下面三個工具可以選用
    Thumbnail
      給對想當Java工程師又興趣的人類,也許你是學生,又或是你正想轉職,我希望你們都不會因為未知而害怕,這條路沒有那麼困難。
    Thumbnail
    1.加權指數與櫃買指數 週五的加權指數在非農就業數據開出來後,雖稍微低於預期,但指數仍向上噴出,在美股開盤後於21500形成一個爆量假突破後急轉直下,就一路收至最低。 台股方面走勢需觀察週一在斷頭潮出現後,週二或週三開始有無買單進場支撐,在沒有明確的反轉訊號形成前,小夥伴盡量不要貿然抄底,或是追空
    Thumbnail
    重點摘要: 1.9 月降息 2 碼、進一步暗示年內還有 50 bp 降息 2.SEP 上修失業率預期,但快速的降息速率將有助失業率觸頂 3.未來幾個月經濟數據將繼續轉弱,經濟復甦的時點或是 1Q25 季底附近
    Thumbnail
    近期的「貼文發佈流程 & 版型大更新」功能大家使用了嗎? 新版式整體視覺上「更加凸顯圖片」,為了搭配這次的更新,我們推出首次貼文策展 ❤️ 使用貼文功能並完成這次的指定任務,還有機會獲得富士即可拍,讓你的美好回憶都可以用即可拍珍藏!
    Thumbnail
    相信很多初學者學python的原因,不外乎語法簡單、好上手、重點是有很多現成的套件可以玩。那麼,Java呢?有!當然有!而且還多到你不知道該選哪個好! 今天的文章主要示範如何在vscode新建立Java 的maven專案,並且透過maven安裝這些額外的套件(依賴)
    Thumbnail
    最近配合公司政策換了新電腦,重新回想起從頭建環境的惡夢。本篇文就來記錄一下如何開始踏入Java的第一步,方便起見也使用相對Eclipse、IntelliJ來說輕量不少的VScode作為編輯器。
    來到學期2-3的階段,第一個作業就是打造餐廳清單。原本認為經過電影清單的學習經歷之後,對於打造餐廳清單應該也不會太過困難;沒想到我花了2個月的時間才把作業完整交出去。 在寫餐廳清單的初期,第一個碰到的問題就是首頁無法秀出餐廳評分這個選項。我試著參考其他同學的作品也改了版面的設計,卻始終無法出現餐廳評
    Thumbnail
    位在通化街尾端,靠近六張犁的禾雀咖啡工作室,質樸的感覺是一家適合假日早晨來待上好一會兒的好去處。 主打自家烘培手沖及義式咖啡,最大特色還有自家製的麵包、甜點輕食,最喜歡吃的莫過於他們的鮮奶吐司。曾經住在這裡好長一段時間,週末去搶個一包可以在家裡烤來吃,是相當幸福的感覺。 冰羅馬咖啡則是我喝過最喜歡的
    Thumbnail
    abstract class = 抽象類別 interface = 介面 抽象類別與介面都無法建立物件。 1. 使用abstract關鍵字來建立抽象類別,interface關鍵字建立介面。 interface只能繼承interface,且可以繼承多個:
    Thumbnail
    繼承 在Java中,一個類可以由其他類派生。如果你要創建一個類,而且已經存在一個類具有你所需要的屬性或方法,那麼你可以將新創建的類繼承該類。 利用繼承的方法,可以重用已存在類的方法和屬性,而不用重寫這些代碼。被繼承的類稱為超類(super class),派生類稱為子類(subclass)。 繼承的特
    Thumbnail
    古早的年代想在網頁內埋 Java 還有 Java applet 可以用,在 Java applet 式微後,找來找去比較可以的辦法大概就是編譯成 WebAssembly 了吧! 想要把 Java 編譯成 WebAssembly,有下面三個工具可以選用
    Thumbnail
      給對想當Java工程師又興趣的人類,也許你是學生,又或是你正想轉職,我希望你們都不會因為未知而害怕,這條路沒有那麼困難。