Java8的Stream API可以極大提高Java程序員的生產(chǎn)力作彤,讓程序員寫出高效率膘魄、干凈乌逐、簡潔的代碼。
那么创葡,Stream API的性能到底如何呢浙踢,代碼整潔的背后是否意味著性能的損耗呢?本文對Stream API的性能一探究竟灿渴。
為保證測試結(jié)果真實(shí)可信洛波,我們將JVM運(yùn)行在 -server模式下,測試數(shù)據(jù)在GB量級骚露,測試機(jī)器采用常見的商用服務(wù)器蹬挤,配置如下:
? OSCentOS 6.7 x86_64
CPUIntel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads
內(nèi)存96GB
JDKjava version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM
測試所用代碼在這里
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee
測試結(jié)果匯總
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/Stream_performance.xlsx
測試方法和測試數(shù)據(jù)
性能測試并不是容易的事,Java性能測試更費(fèi)勁棘幸,因?yàn)樘摂M機(jī)對性能的影響很大焰扳,JVM對性能的影響有兩方面:
1、GC的影響误续。GC的行為是Java中很不好控制的一塊吨悍,為增加確定性,我們手動(dòng)指定使用CMS收集器蹋嵌,并使用10GB固定大小的堆內(nèi)存育瓜。集體到JVM參數(shù)就是 -XX:+UseConcMarkSweepGC-Xms10G-Xmx10G
2、JIT(Just-In-Time)即時(shí)編譯技術(shù)栽烂。即時(shí)編譯技術(shù)會將熱點(diǎn)代碼在JVM運(yùn)行的過程中編譯成本地代碼躏仇,測試時(shí)我們會先對程序預(yù)熱恋脚,觸發(fā)對測試函數(shù)的即時(shí)編譯。相關(guān)的JVM參數(shù)是 -XX:CompileThreshold=10000焰手。
Stream并行執(zhí)行時(shí)用到 ForkJoinPool.commonPool()得到的線程池慧起,為控制并行度我們使用Linux的 taskset命令指定JVM可用的核數(shù)。
測試數(shù)據(jù)由程序隨機(jī)生成册倒。為防止一次測試帶來的抖動(dòng)蚓挤,測試4次求出平均時(shí)間作為運(yùn)行時(shí)間。
實(shí)驗(yàn)一 基本類型迭代
測試內(nèi)容:找出整型數(shù)組中的最小值驻子。對比for循環(huán)外部迭代和Stream API內(nèi)部迭代性能灿意。
測試程序 IntTest
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee/IntTest.java
測試結(jié)果如下圖:
圖中展示的是for循環(huán)外部迭代耗時(shí)為基準(zhǔn)的時(shí)間比值。分析如下:
1崇呵、對于基本類型Stream串行迭代的性能開銷明顯高于外部迭代開銷(兩倍)缤剧;
2、Stream并行迭代的性能比串行迭代和外部迭代都好域慷。
并行迭代性能跟可利用的核數(shù)有關(guān)荒辕,上圖中的并行迭代使用了全部12個(gè)核,為考察使用核數(shù)對性能的影響犹褒,我們專門測試了不同核數(shù)下的Stream并行迭代效果:
分析抵窒,對于基本類型:
1、使用Stream并行API在單核情況下性能很差叠骑,比Stream串行API的性能還差李皇;
2、隨著使用核數(shù)的增加宙枷,Stream并行效果逐漸變好掉房,比使用for循環(huán)外部迭代的性能還好。
以上兩個(gè)測試說明慰丛,對于基本類型的簡單迭代卓囚,Stream串行迭代性能更差,但多核情況下Stream迭代時(shí)性能較好诅病。
實(shí)驗(yàn)二 對象迭代
再來看對象的迭代效果哪亿。
測試內(nèi)容:找出字符串列表中最小的元素(自然順序),對比for循環(huán)外部迭代和Stream API內(nèi)部迭代性能睬隶。
測試程序StringTest
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee/StringTest.java
對 Stream 不熟悉的锣夹,可以關(guān)注微信公眾號:Java技術(shù)棧,在后臺回復(fù):Java苏潜。
測試結(jié)果如下圖:
結(jié)果分析如下:
1银萍、對于對象類型Stream串行迭代的性能開銷仍然高于外部迭代開銷(1.5倍),但差距沒有基本類型那么大恤左。
2、Stream并行迭代的性能比串行迭代和外部迭代都好。
再來單獨(dú)考察Stream并行迭代效果:
分析征唬,對于對象類型:
1义郑、使用Stream并行API在單核情況下性能比for循環(huán)外部迭代差近范;
2、隨著使用核數(shù)的增加,Stream并行效果逐漸變好,多核帶來的效果明顯麻捻。
以上兩個(gè)測試說明,對于對象類型的簡單迭代呀袱,Stream串行迭代性能更差贸毕,但多核情況下Stream迭代時(shí)性能較好。
實(shí)驗(yàn)三 復(fù)雜對象歸約
從實(shí)驗(yàn)一夜赵、二的結(jié)果來看明棍,Stream串行執(zhí)行的效果都比外部迭代差(很多),是不是說明Stream真的不行了寇僧?先別下結(jié)論摊腋,我們再來考察一下更復(fù)雜的操作。
測試內(nèi)容:給定訂單列表嘁傀,統(tǒng)計(jì)每個(gè)用戶的總交易額兴蒸。對比使用外部迭代手動(dòng)實(shí)現(xiàn)和Stream API之間的性能。
我們將訂單簡化為 <userName,price,timeStamp>構(gòu)成的元組心包,并用 Order對象來表示类咧。測試程序ReductionTest
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee/ReductionTest.java
測試結(jié)果如下圖:
分析馒铃,對于復(fù)雜的歸約操作:
1蟹腾、Stream API的性能普遍好于外部手動(dòng)迭代,并行Stream效果更佳区宇;
再來考察并行度對并行效果的影響娃殖,測試結(jié)果如下:
分析,對于復(fù)雜的歸約操作:
1议谷、使用Stream并行歸約在單核情況下性能比串行歸約以及手動(dòng)歸約都要差炉爆,簡單說就是最差的;
2卧晓、隨著使用核數(shù)的增加芬首,Stream并行效果逐漸變好,多核帶來的效果明顯逼裆。
以上兩個(gè)實(shí)驗(yàn)說明郁稍,對于復(fù)雜的歸約操作,Stream串行歸約效果好于手動(dòng)歸約胜宇,在多核情況下耀怜,并行歸約效果更佳恢着。我們有理由相信,對于其他復(fù)雜的操作财破,Stream API也能表現(xiàn)出相似的性能表現(xiàn)掰派。
結(jié)論
上述三個(gè)實(shí)驗(yàn)的結(jié)果可以總結(jié)如下:
1、對于簡單操作左痢,比如最簡單的遍歷靡羡,Stream串行API性能明顯差于顯示迭代,但并行的Stream API能夠發(fā)揮多核特性俊性。
2亿眠、對于復(fù)雜操作,Stream串行API性能可以和手動(dòng)實(shí)現(xiàn)的效果匹敵磅废,在并行執(zhí)行時(shí)Stream API效果遠(yuǎn)超手動(dòng)實(shí)現(xiàn)纳像。
所以,如果出于性能考慮
1拯勉、對于簡單操作推薦使用外部迭代手動(dòng)實(shí)現(xiàn)?
2竟趾、對于復(fù)雜操作,推薦使用Stream API?
3宫峦、在多核情況下岔帽,推薦使用并行Stream API來發(fā)揮多核優(yōu)勢,?
4导绷、單核情況下不建議使用并行Stream API犀勒。
如果出于代碼簡潔性考慮,使用Stream API能夠?qū)懗龈痰拇a妥曲。即使是從性能方面說贾费,盡可能的使用Stream API也另外一個(gè)優(yōu)勢,那就是只要Java Stream類庫做了升級優(yōu)化檐盟,代碼不用做任何修改就能享受到升級帶來的好處褂萧。
作者:CarpenterLee
來源:https://dwz.cn/pSW0u0Qr