Java 8 Stream的性能到底如何樟氢?

那么,Stream API的性能到底如何呢侠鳄,代碼整潔的背后是否意味著性能的損耗呢埠啃?本文我們對Stream API的性能一探究竟。

為保證測試結(jié)果真實可信伟恶,我們將JVM運行在 -server 模式下碴开,測試數(shù)據(jù)在GB量級,測試機器采用常見的商用服務(wù)器博秫,配置如下:

OSCentOS 6.7 x86_64CPUIntel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads內(nèi)存96GBJDKjava version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM

測試方法和測試數(shù)據(jù)

性能測試并不是容易的事潦牛,Java性能測試更費勁,因為虛擬機對性能的影響很大挡育,JVM對性能的影響有兩方面:

-XX:+UseConcMarkSweepGC -Xms10G -Xmx10G-XX:CompileThreshold=10000

Stream并行執(zhí)行時用到 ForkJoinPool.commonPool() 得到的線程池巴碗,為控制并行度我們使用Linux的 taskset 命令指定JVM可用的核數(shù)。

測試數(shù)據(jù)由程序隨機生成即寒。為防止一次測試帶來的抖動橡淆,測試4次求出平均時間作為運行時間。

實驗一 基本類型迭代

測試內(nèi)容:找出整型數(shù)組中的最小值母赵。對比for循環(huán)外部迭代和Stream API內(nèi)部迭代性能逸爵。

測試程序代碼:

/**

* java -server -Xms10G -Xmx10G -XX:+PrintGCDetails

* -XX:+UseConcMarkSweepGC -XX:CompileThreshold=1000 lee/IntTest

* taskset -c 0-[0,1,3,7] java ...

* @author CarpenterLee

*/publicclassIntTest{publicstaticvoidmain(String[] args){newIntTest().doTest(); }publicvoiddoTest(){ warmUp();int[] lengths = {10000,100000,1000000,10000000,100000000,1000000000};for(intlength : lengths){ System.out.println(String.format("---array length: %d---", length));int[] arr =newint[length]; randomInt(arr);inttimes =4;intmin1 =1;intmin2 =2;intmin3 =3;longstartTime; startTime = System.nanoTime();for(inti=0; i

測試結(jié)果如下圖:

圖中展示的是for循環(huán)外部迭代耗時為基準的時間比值。分析如下:

對于基本類型Stream串行迭代的性能開銷明顯高于外部迭代開銷(兩倍)凹嘲;

Stream并行迭代的性能比串行迭代和外部迭代都好师倔。

并行迭代性能跟可利用的核數(shù)有關(guān),上圖中的并行迭代使用了全部12個核周蹭,為考察使用核數(shù)對性能的影響溯革,我們專門測試了不同核數(shù)下的Stream并行迭代效果:

分析,對于基本類型:

使用Stream并行API在單核情況下性能很差谷醉,比Stream串行API的性能還差致稀;

隨著使用核數(shù)的增加,Stream并行效果逐漸變好俱尼,比使用for循環(huán)外部迭代的性能還好抖单。

以上兩個測試說明,對于基本類型的簡單迭代遇八,Stream串行迭代性能更差矛绘,但多核情況下Stream迭代時性能較好。

實驗二 對象迭代

再來看對象的迭代效果刃永。

測試內(nèi)容:找出字符串列表中最小的元素(自然順序)货矮,對比for循環(huán)外部迭代和Stream API內(nèi)部迭代性能。

測試程序代碼:

/**

* java -server -Xms10G -Xmx10G -XX:+PrintGCDetails

* -XX:+UseConcMarkSweepGC -XX:CompileThreshold=1000 lee/StringTest

* taskset -c 0-[0,1,3,7] java ...

* @author CarpenterLee

*/publicclass StringTest {publicstaticvoidmain(String[] args) {newStringTest().doTest(); }publicvoiddoTest(){ warmUp();int[] lengths = {10000,100000,1000000,10000000,20000000,40000000};for(intlength : lengths){ System.out.println(String.format("---List length: %d---", length)); ArrayList list = randomStringList(length);inttimes =4;Stringmin1 ="1";Stringmin2 ="2";Stringmin3 ="3";longstartTime; startTime = System.nanoTime();for(inti=0; i list = randomStringList(10);for(inti=0; i<20000; i++){ minStringForLoop(list); minStringStream(list); minStringParallelStream(list); } }privateStringminStringForLoop(ArrayList list){StringminStr =null;booleanfirst =true;for(Stringstr: list){if(first){ first =false; minStr =str; }if(minStr.compareTo(str)>0){ minStr =str; } }returnminStr; }privateStringminStringStream(ArrayList list){returnlist.stream().min(String::compareTo).get(); }privateStringminStringParallelStream(ArrayList list){returnlist.stream().parallel().min(String::compareTo).get(); }privateArrayList randomStringList(intlistLength){ ArrayList list =newArrayList<>(listLength); Random rand =newRandom();intstrLength =10; StringBuilder buf =newStringBuilder(strLength);for(inti=0; i

測試結(jié)果如下圖:

結(jié)果分析如下:

對于對象類型Stream串行迭代的性能開銷仍然高于外部迭代開銷(1.5倍)斯够,但差距沒有基本類型那么大囚玫。

Stream并行迭代的性能比串行迭代和外部迭代都好喧锦。

再來單獨考察Stream并行迭代效果:

分析,對于對象類型:

使用Stream并行API在單核情況下性能比for循環(huán)外部迭代差抓督;

隨著使用核數(shù)的增加燃少,Stream并行效果逐漸變好,多核帶來的效果明顯铃在。

以上兩個測試說明阵具,對于對象類型的簡單迭代,Stream串行迭代性能更差定铜,但多核情況下Stream迭代時性能較好阳液。

實驗三 復(fù)雜對象歸約

從實驗一、二的結(jié)果來看揣炕,Stream串行執(zhí)行的效果都比外部迭代差(很多)趁舀,是不是說明Stream真的不行了?先別下結(jié)論祝沸,我們再來考察一下更復(fù)雜的操作。

測試內(nèi)容:給定訂單列表越庇,統(tǒng)計每個用戶的總交易額罩锐。對比使用外部迭代手動實現(xiàn)和Stream API之間的性能。

我們將訂單簡化為 <userName, price, timeStamp> 構(gòu)成的元組卤唉,并用 Order 對象來表示涩惑。

測試程序代碼:

/**

* java -server -Xms10G -Xmx10G -XX:+PrintGCDetails

* -XX:+UseConcMarkSweepGC -XX:CompileThreshold=1000 lee/ReductionTest

* taskset -c 0-[0,1,3,7] java ...

* @author CarpenterLee

*/publicclass ReductionTest {publicstaticvoidmain(String[] args) {newReductionTest().doTest(); }publicvoiddoTest(){ warmUp();int[] lengths = {10000,100000,1000000,10000000,20000000,40000000};for(intlength : lengths){ System.out.println(String.format("---orders length: %d---", length)); List orders = Order.genOrders(length);inttimes =4; Map map1 =null; Map map2 =null; Map map3 =null;longstartTime; startTime = System.nanoTime();for(inti=0; i orders = Order.genOrders(10);for(inti=0; i<20000; i++){ sumOrderForLoop(orders); sumOrderStream(orders); sumOrderParallelStream(orders); } }privateMap sumOrderForLoop(List orders){ Mapmap=newHashMap<>();for(Order od : orders){StringuserName = od.getUserName(); Double v;if((v=map.get(userName)) !=null){map.put(userName, v+od.getPrice()); }else{map.put(userName, od.getPrice()); } }returnmap; }privateMap sumOrderStream(List orders){returnorders.stream().collect( Collectors.groupingBy(Order::getUserName,? Collectors.summingDouble(Order::getPrice))); }privateMap sumOrderParallelStream(List orders){returnorders.parallelStream().collect( Collectors.groupingBy(Order::getUserName,? Collectors.summingDouble(Order::getPrice))); }}class Order{privateStringuserName;privatedoubleprice;privatelongtimestamp;publicOrder(StringuserName,doubleprice,longtimestamp) {this.userName = userName;this.price = price;this.timestamp = timestamp; }publicStringgetUserName() {returnuserName; }publicdoublegetPrice() {returnprice; }publiclonggetTimestamp() {returntimestamp; }publicstaticList genOrders(intlistLength){ ArrayList list =newArrayList<>(listLength); Random rand =newRandom();intusers = listLength/200;// 200 orders per userusers = users==0? listLength : users; ArrayList userNames =newArrayList<>(users);for(inti=0; i

測試結(jié)果如下圖:

分析,對于復(fù)雜的歸約操作:

Stream API的性能普遍好于外部手動迭代桑驱,并行Stream效果更佳竭恬;

再來考察并行度對并行效果的影響,測試結(jié)果如下:

分析熬的,對于復(fù)雜的歸約操作:

使用Stream并行歸約在單核情況下性能比串行歸約以及手動歸約都要差痊硕,簡單說就是最差的;

隨著使用核數(shù)的增加押框,Stream并行效果逐漸變好岔绸,多核帶來的效果明顯。

以上兩個實驗說明橡伞,對于復(fù)雜的歸約操作盒揉,Stream串行歸約效果好于手動歸約,在多核情況下兑徘,并行歸約效果更佳刚盈。我們有理由相信,對于其他復(fù)雜的操作挂脑,Stream API也能表現(xiàn)出相似的性能表現(xiàn)藕漱。

結(jié)論

上述三個實驗的結(jié)果可以總結(jié)如下:

對于簡單操作欲侮,比如最簡單的遍歷,Stream串行API性能明顯差于顯示迭代谴分,但并行的Stream API能夠發(fā)揮多核特性锈麸。

對于復(fù)雜操作,Stream串行API性能可以和手動實現(xiàn)的效果匹敵牺蹄,在并行執(zhí)行時Stream API效果遠超手動實現(xiàn)忘伞。

所以,如果出于性能考慮沙兰,1. 對于簡單操作推薦使用外部迭代手動實現(xiàn)氓奈,2. 對于復(fù)雜操作,推薦使用Stream API鼎天, 3. 在多核情況下舀奶,推薦使用并行Stream API來發(fā)揮多核優(yōu)勢,4.單核情況下不建議使用并行Stream API斋射。

如果出于代碼簡潔性考慮育勺,使用Stream API能夠?qū)懗龈痰拇a。即使是從性能方面說罗岖,盡可能的使用Stream API也另外一個優(yōu)勢涧至,那就是只要Java Stream類庫做了升級優(yōu)化,代碼不用做任何修改就能享受到升級帶來的好處桑包。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末南蓬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子哑了,更是在濱河造成了極大的恐慌赘方,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弱左,死亡現(xiàn)場離奇詭異窄陡,居然都是意外死亡,警方通過查閱死者的電腦和手機拆火,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門泳梆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人榜掌,你說我怎么就攤上這事优妙。” “怎么了憎账?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵套硼,是天一觀的道長。 經(jīng)常有香客問我胞皱,道長邪意,這世上最難降的妖魔是什么九妈? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮雾鬼,結(jié)果婚禮上萌朱,老公的妹妹穿的比我還像新娘。我一直安慰自己策菜,他們只是感情好晶疼,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著又憨,像睡著了一般翠霍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蠢莺,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天寒匙,我揣著相機與錄音,去河邊找鬼躏将。 笑死锄弱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的祸憋。 我是一名探鬼主播会宪,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夺衍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起喜命,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤沟沙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后壁榕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矛紫,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年牌里,在試婚紗的時候發(fā)現(xiàn)自己被綠了颊咬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡牡辽,死狀恐怖喳篇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情态辛,我是刑警寧澤麸澜,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站奏黑,受9級特大地震影響炊邦,放射性物質(zhì)發(fā)生泄漏编矾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一馁害、第九天 我趴在偏房一處隱蔽的房頂上張望窄俏。 院中可真熱鬧,春花似錦碘菜、人聲如沸凹蜈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽踪区。三九已至,卻和暖如春吊骤,著一層夾襖步出監(jiān)牢的瞬間缎岗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工白粉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留传泊,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓鸭巴,卻偏偏與公主長得像眷细,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鹃祖,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容