fork join框架_Fork / Join框架vs.并行流vs.ExecutorService:最终的Fork / Join基准
fork join框架
Fork / Join框架在不同配置下如何工作?
就像即將到來的《星球大戰》(Star Wars)一樣,圍繞Java 8并行性的批評也充滿了興奮。 并行流的語法糖帶來了一些炒作,就像我們在預告片中看到的新型光劍一樣。 現在,有了許多使用Java進行并行處理的方法,我們希望了解性能優勢和并行處理的危險。 經過260多次測試運行后,從數據中獲得了一些新見解,我們希望在本文中與您分享。
分叉/加入:分叉喚醒
ExecutorService與Fork / Join Framework與并行流
很久以前,在一個遙遠的星系中……。 我的意思是,大約10年前,并發只能通過3rd party庫在Java中使用。 隨后出現了Java 5,并在語言中引入了java.util.concurrent庫,該庫在Doug Lea的強烈影響下。 ExecutorService可用并為我們提供了一種處理線程池的直接方法。 當然,java.util.concurrent一直在發展,并且在Java 7中引入了Fork / Join框架,該框架建立在ExecutorService線程池之上。 使用Java 8流,已經為我們提供了使用Fork / Join的簡單方法,但對于許多開發人員來說仍然有點謎。 讓我們找出它們之間的比較。
我們完成了兩項任務,一項是CPU密集型任務,另一項是IO密集型任務,并使用相同的基本功能測試了4種不同的方案。 另一個重要因素是我們用于每個實現的線程數,因此我們也對其進行了測試。 我們使用的機器有8個內核 ,因此我們有4、8、16和32個線程的變種,以大致了解結果的發展方向。 對于每個任務,我們還嘗試了單線程解決方案,您不會在圖中看到它,因為執行起來要花費更長的時間。 要詳細了解測試的運行方式,您可以查看下面的基礎部分。 現在,讓我們開始吧。
用580萬行文本索引6GB文件
在此測試中,我們生成了一個巨大的文本文件,并為索引過程創建了類似的實現。 結果如下所示:
文件索引測試結果
**單線程執行:176,267毫秒,或將近3分鐘。
**請注意,圖形開始于20000毫秒。
1.更少的線程將使CPU未被使用,太多的線程將增加開銷
您在圖表中注意到的第一件事是結果開始采用的形狀–您僅從這4個數據點就可以了解每個實現的行為。 臨界點在8到16個線程之間,因為某些線程在文件IO中處于阻塞狀態,并且添加比內核更多的線程有助于更好地利用它們。 當有32個線程進入時,由于額外的開銷,性能會變差。
比亞軍快1秒:直接使用Fork / Join
除了語法糖(lambdas!我們沒有提到lambdas),我們已經看到并行流的性能比Fork / Join和ExecutorService實現的更好。 6GB的文本在24.33秒內被索引。 您可以在這里信任Java來提供最佳結果。
3.但是…并行流也表現最差:唯一的變化超過了30秒
這再次提醒您并行流如何降低您的速度。 假設這種情況發生在已經運行多線程應用程序的計算機上。 在可用線程數量較少的情況下,直接使用Fork / Join實際上比通過并行流要好-5秒的差異,將這兩個線程進行比較時大約要付出18%的代價。
4.不要使用圖片中帶有IO的默認池大小
當為并行流使用默認池大小時,機器上相同數量的內核(此處為8個內核)比16線程版本的性能差了近2秒。 如果使用默認池大小,則要加收7%的罰款。 發生這種情況的原因與阻塞IO線程有關。 還有更多的等待正在進行,因此引入更多的線程可以使我們更多地使用所涉及的CPU內核,而其他線程可以等待調度而不是空閑。
如何更改并行流的默認Fork / Join池大小? 您可以使用JVM參數更改常見的Fork / Join池大小:
-Djava.util.concurrent.ForkJoinPool.common.parallelism=16(默認情況下,所有Fork / Join任務都使用一個公共靜態池,其大小與內核數相同。這樣做的好處是,通過在不使用期間為其他任務回收線程,從而減少了資源使用。)
或者…您可以使用此技巧并在自定義Fork / Join池中運行并行流。 這將覆蓋通用的Fork / Join池的默認用法,并允許您使用自己設置的池。 偷偷摸摸。 在測試中,我們使用了公共池。
5.單線程性能比最佳結果差7.25倍
并行性提供了7.25倍的改進,并且考慮到該機器具有8個內核,因此非常接近理論上的8倍預測! 我們可以將其余的歸因于開銷。 話雖如此,即使我們測試的最慢的并行性實現(這次是具有4個線程的并行流(30.24sec)),其性能也比單線程解決方案(176.27sec)好5.8倍。
檢查數字是否為質數
在下一輪測試中,我們完全消除了IO,并檢查了確定一個真正大的數字是否為素數所需的時間。 多大? 19位數字 。 1,530,692,068,127,007,263或換句話說:五百一十九億四千三百四十四萬億三千三百八十億八千八百八十三萬三千三百三十。 啊,讓我呼吸一下。 無論如何,除了運行到其平方根之外,我們沒有使用任何優化,因此即使我們的大數沒有除以2只是為了延長處理時間,我們也檢查了所有偶數。 劇透警告:這是首要的,因此每個實現都運行相同數量的計算。
結果是這樣的:
素數測試結果
**單線程執行:118,127毫秒,或將近2分鐘。
**請注意,圖形開始于20000毫秒
1. 8和16個線程之間的差異較小
與IO測試不同,這里沒有IO調用,因此8和16線程的性能幾乎是相似的,除了Fork / Join解決方案。 實際上,我們已經運行了更多的測試集,以確保由于這種“異常”而在這里獲得了良好的結果,但事實證明,一次又一次的相似。 我們很高興在下面的評論部分中聽到您對此的想法。
2.所有方法的最佳結果相似
我們看到所有實現都分享了大約28秒的相似最佳結果。 無論我們嘗試采用哪種方法,結果都相同。 這并不意味著我們對使用哪種方法都無所謂。 查看下一個見解。
3.并行流比其他實現更好地處理線程重載
這是更有趣的部分。 通過該測試,我們再次看到運行16個線程的最高結果來自使用并行流。 此外,在此版本中,使用并行流是線程號所有變體的一個好選擇。
4.單線程性能比最佳結果低4.2倍
此外,在運行計算密集型任務時使用并行性的好處幾乎比使用文件IO的IO測試要差2倍。 這是有道理的,因為它是CPU密集型測試,與之前的測試不同,我們可以通過減少內核等待被IO阻塞的線程的時間來獲得額外的好處。
結論
我建議您從源頭上了解何時使用并行流的更多信息,并在您使用Java進行并行化時隨時進行仔細的判斷。 最好的方法是在暫存環境中運行與這些測試類似的測試,在此環境中您可以嘗試并更好地了解要面對的挑戰。 您必須注意的因素當然是運行的硬件(以及要測試的硬件)以及應用程序中的線程總數。 這包括公用的Fork / Join池和團隊中其他開發人員正在處理的代碼。 因此,在添加自己的并行性之前,請嘗試檢查它們并獲得應用程序的完整視圖。
基礎工作
為了運行此測試,我們使用了具有8個vCPU和15GB RAM的EC2 c3.2xlarge實例。 vCPU意味著存在超線程,因此實際上我們在這里有4個物理內核,每個物理內核都像2個內核一樣工作。就OS調度程序而言,我們在這里有8個內核。 為了盡可能使它公平,每個實現都運行了10次,并且我們采用了運行2到9的平均運行時間。這是260次測試運行,! 另一重要的是處理時間。 我們選擇的任務要花費20秒鐘以上的時間,因此差異更容易發現,并且不受外部因素的影響。
下一步是什么?
原始結果可在此處獲得 ,代碼在GitHub上 。 請隨時修改它,并讓我們知道您得到什么樣的結果。 如果您對我們錯過的結果有更多有趣的見解或解釋,我們很樂意閱讀并添加到帖子中。
翻譯自: https://www.javacodegeeks.com/2015/01/forkjoin-framework-vs-parallel-streams-vs-executorservice-the-ultimate-forkjoin-benchmark.html
fork join框架
總結
以上是生活随笔為你收集整理的fork join框架_Fork / Join框架vs.并行流vs.ExecutorService:最终的Fork / Join基准的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 老式烧饼的做法 老式烧饼的具体做法
- 下一篇: 牛油果产地 牛油果产地在哪