最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

effective java 3-第7章 lambda和stream[48]謹慎使用Stream并行

2023-03-30 20:21 作者:CC挑燈夜讀_谷  | 我要投稿

????在主流編程語言中,Java一直走在簡化并發(fā)編程任務的最前沿。1996年Java發(fā)布時,就通過同步和wait/notify內置了對線程的支持。Java5?引入了java.util.concurrent類庫,提供了并行集包(concurrent collection)和執(zhí)行者框架(executor framework) 。Java7引入了 fork-join包,這是一個處理并行分解的高性能框架。Java8引入了Stream,只需要調用一次parallel方法就可以實現并行處理。在Java中編寫并發(fā)程序變得越來越容易,但是要編寫出正確又快速的并發(fā)程序,則一向沒那么簡單。安全性和活性失敗是并發(fā)編程中需要面對的問題,Stream pipeline并行也不例外。

????請看摘自45條的這段程序:

????在我的機器上,這段程序會立即開始打印素數,玩完成運行花了12.5秒。假設我天真地想通過在Stream pipeline 上添加一個parallel() 調用來提速。你認為這樣會對其性能產生什么樣的影響呢?運行速度會稍微快一點點嗎?還是會慢一點點?遺憾的是,其結果是根本不打印任何內容了,CPU的使用率卻定在90%一動不動了(活性失?。?。程序最后可能會終止,但是我不想一探究竟,半個小時后就強行把它終止了。

????這是怎么回事呢?簡單地說,Stream類庫不知道如何并行這個pipeline,以及如何探索失敗。即便在最佳環(huán)境下,如果源頭是來自于Stream.iterator,或者使用了中間操作的limit,那么并行pipeline 也不可能提升性能。這個pipeline 必須同時滿足這兩個條件。更糟糕的是,默認的并行策略在處理limit 的不可預知性時,是假設額外多處理幾個元素,并放棄任何不需要的結果,這些都不會影響性能。在這種情況下,它查找梅森素數時,所花費的時間大概是查找之前元素的兩倍。因而,額外多計算一個元素的成本,大概相當于計算所有之前元素總和的時間,這個貌似無傷大雅的pipeline,卻使得自動并行算法瀕臨崩潰。這個故事的寓意很簡單:千萬不要任意地并行Stream pipeline。它造成的性能后果有可能是災難性的。

????總之,在Stream 上通過并行獲得的性能,最好是通過ArrayList、HashMap、HashSet 和ConcurrentHashMap 實例,數組,int 范圍和long 范圍等。這些數據結果的共性是,都可以被精確、輕松地分成任意大小的子范圍,使并行線程中的分工變得更加輕松。Stream 類庫用來執(zhí)行這個任務的抽象是分割迭代器(spliterator),它是由Stream 和 Iterable 中的 spliterator方法返回。

????這些數據結構共有e另一項重要特性是,在進行順序處理時,它們提供了優(yōu)異的引用局部性(locality of reference):序列化的元素引用一起保存在內存中。被那些引用訪問到的對象在內存中可能不是一個緊挨著一個,這降低了引用局部性。事實證明,引用局部性對于并行批處理來說至關重要:沒有它,線程就會出現閑置,需要等待數據從內存轉移到處理器的緩存。具有最佳引用局部性的數據結構是基本類型數據數組,因為數據本身是相鄰地保存在內存中的。

????Stream pipeline 的終止操作本質上也影響了并發(fā)執(zhí)行的效率。如果大量的工作在終止操作中完成,而不是全部工作在pipeline中完成,并且這個操作是固有的順序,那么并行pipeline的效率就會受到限制。并行的最佳終止操作是做減法(reduction),用一個Stream的reduce方法,將所有從pipeline產生的元素都合并在一起,或者預先打包像min、max、count、和sum這類方法。驟死式操作(short-circuiting operation)如 anyMach、allMatch和noneMatch也都可以并行。由Stream的collect方法執(zhí)行的操作,都是可變的減法,不是并行的的最好選擇,因為合并集合的成本非常高。

????如果是自己編寫Stream 、Iterable或者Collection實現,并且想要得到適當的并行性能,就必須覆蓋spliterator方法,并廣泛地測試結果Stream 的并行性能。編寫高質量的分隔迭代器很困難,并且超出了本書的討論范疇。

????并行Stream 不僅可能降低性能,包括活性失敗,還可能導致結果出錯,以及難以預計的行為(如安全性失?。0踩允】赡苁且驗椴⑿械膒ipeline使用了映射、過濾器或者程序員自己編寫的其他函數對象,并且沒有遵守他們的規(guī)范。Stream 規(guī)范對于這些函數對象有著嚴格的要求條件。例如,傳到Stream的reduce操作的收集器函數和組合器函數,必須是有關聯、互不干擾,并且是無狀態(tài)的。如果不滿足這些條件(在46條中提到了一些),但是按序列運行pipeline,可能會得到正確的結果;如果并發(fā)運行,則可能會突發(fā)性失敗。

????以上值得注意的是,并行的梅森素數程序雖然運行完成了,但是并沒有按正確的順序(升序)打印出來。為了保存序列化版本程序顯示的順序,必須用forEachOrdered代替終止操作的forEach,它可以確保按encounter順序遍歷并行的Stream。

????假如在使用的是一個可以有效分隔的源Stream,一個可并行的或者簡單的終止操作,以及互不干擾的函數對象,那么將無法獲得通過并行實現的提速,除非pipeline完成了足夠的實際工作,抵消了與并行相關的成本。據不完全統(tǒng)計,Stream中的元素數量是每個元素所執(zhí)行的代碼行數的很多倍,至少是十萬倍[Lea 14]。

????切記:并行Stream 是一項嚴格的性能優(yōu)化。對于任何優(yōu)化都必須在改變前后對性能進行測試,以確保值得這么做(item 67)。最理想的是在現實的系統(tǒng)設置中進行測試。一般來說,程序中所有并行Stream pipeline 都是在一個通用的fork-join池中運行得。只要有一個pipeline 運行異常,都會損害到系統(tǒng)中其他不相關部分的性能。

????聽起來貌似在并行Stream pipeline 時怪事連連,其實正是如此。我有個朋友,他發(fā)現在大量使用Stream 的幾百萬行代碼中,只有少數幾個并行Stream 是有效的。這并不意味著應該避免使用并行Stream 。在適當的條件下,給Stream pipeline 添加 parallel 調用,確實可以在多處理器核的情況下實現近乎線性的倍增。某些域如機器學習和數據處理,尤其適用于這樣的提速。

????簡單舉一個并行Stream pipeline 有效的例子。假設下面這個函數是用來計算?π(n) ,素數的數量少于或者等于n :


????在我的(作者)機器上,這個函數花31秒完成了計算?π(10^8)?。只要添加一個 parallel() 調用,就把調用時間減少到了9.2s

????換句話說,并行計算在我的四核機器上添加了parallel 調用后,速度加快了3.7倍。值得注意的是,這并不是在實踐中計算n值很大時的?π(n) 的方法。還有更加高效的算法,如著名的Lehmer公式。

????如果要并行一個隨機數的Stream ,應該從SplittableRandom實例開始,而不是從ThreadLocalRandom(或實際上已經過時的Random)開始。SplittableRandom正是專門為此設計的,還有線性提速的可能。ThreadLocalRandom 則只用于單線程,它將自身當做一個并行的Stream 源運用到函數中,但是沒有SplittableRandom 那么快。Random在每個操作上都進行同步,因此會導致濫用,扼殺了并行的優(yōu)勢。

????總而言之,盡量不要并行Stream pipeline ,除非有足夠的理由相信它能保證計算的正確性,并且能加快程序的運行速度。如果對Stream 進行不恰當的并行操作,可能導致程序運行失敗,或者造成性能災難。如果確信并行是可行的,并發(fā)運行時一定要確保代碼正確,并在真實環(huán)境下認真地進行性能測量。如果代碼正確,這些實驗也證明它有助于提升性能,只有這時候,才可以在編寫代碼時并行Stream。

effective java 3-第7章 lambda和stream[48]謹慎使用Stream并行的評論 (共 條)

分享到微博請遵守國家法律
那曲县| 黄大仙区| 东乌珠穆沁旗| 伊川县| 门源| 镶黄旗| 延寿县| 嵊州市| 八宿县| 天镇县| 汶上县| 武山县| 连云港市| 大名县| 台中县| 延寿县| 黎川县| 永定县| 台江县| 崇礼县| 大庆市| 云安县| 康平县| 利辛县| 安龙县| 东明县| 温泉县| 顺平县| 衡阳市| 临澧县| 菏泽市| 靖远县| 高陵县| 沙河市| 陆川县| 巴南区| 敖汉旗| 防城港市| 彭泽县| 阿拉善盟| 台安县|