Java8的Stream API可以極大提高Java程序員的生產(chǎn)力春瞬,讓程序員寫出高效率眶俩、干凈、簡(jiǎn)潔的代碼快鱼。
那么颠印,Stream API的性能到底如何呢,代碼整潔的背后是否意味著性能的損耗呢抹竹?本文對(duì)Stream API的性能一探究竟线罕。
為保證測(cè)試結(jié)果真實(shí)可信,我們將JVM運(yùn)行在 -server模式下窃判,測(cè)試數(shù)據(jù)在GB量級(jí)钞楼,測(cè)試機(jī)器采用常見的商用服務(wù)器,配置如下:
測(cè)試所用代碼在這里
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee
測(cè)試結(jié)果匯總
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/Stream_performance.xlsx
測(cè)試方法和測(cè)試數(shù)據(jù)
性能測(cè)試并不是容易的事袄琳,Java性能測(cè)試更費(fèi)勁询件,因?yàn)樘摂M機(jī)對(duì)性能的影響很大燃乍,JVM對(duì)性能的影響有兩方面:
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ù)會(huì)將熱點(diǎn)代碼在JVM運(yùn)行的過(guò)程中編譯成本地代碼英古,測(cè)試時(shí)我們會(huì)先對(duì)程序預(yù)熱,觸發(fā)對(duì)測(cè)試函數(shù)的即時(shí)編譯昙读。相關(guān)的JVM參數(shù)是 -XX:CompileThreshold=10000召调。
Stream并行執(zhí)行時(shí)用到 ForkJoinPool.commonPool()得到的線程池,為控制并行度我們使用Linux的 taskset命令指定JVM可用的核數(shù)蛮浑。
測(cè)試數(shù)據(jù)由程序隨機(jī)生成某残。為防止一次測(cè)試帶來(lái)的抖動(dòng),測(cè)試4次求出平均時(shí)間作為運(yùn)行時(shí)間陵吸。
實(shí)驗(yàn)一 基本類型迭代
測(cè)試內(nèi)容:找出整型數(shù)組中的最小值。對(duì)比f(wàn)or循環(huán)外部迭代和Stream API內(nèi)部迭代性能介牙。
測(cè)試程序 IntTest
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee/IntTest.java
測(cè)試結(jié)果如下圖:
圖中展示的是for循環(huán)外部迭代耗時(shí)為基準(zhǔn)的時(shí)間比值壮虫。分析如下:
1、對(duì)于基本類型Stream串行迭代的性能開銷明顯高于外部迭代開銷(兩倍)环础;
2囚似、Stream并行迭代的性能比串行迭代和外部迭代都好。
并行迭代性能跟可利用的核數(shù)有關(guān)线得,上圖中的并行迭代使用了全部12個(gè)核饶唤,為考察使用核數(shù)對(duì)性能的影響,我們專門測(cè)試了不同核數(shù)下的Stream并行迭代效果:
分析贯钩,對(duì)于基本類型:
1募狂、使用Stream并行API在單核情況下性能很差,比Stream串行API的性能還差角雷;
2祸穷、隨著使用核數(shù)的增加,Stream并行效果逐漸變好勺三,比使用for循環(huán)外部迭代的性能還好雷滚。
以上兩個(gè)測(cè)試說(shuō)明,對(duì)于基本類型的簡(jiǎn)單迭代吗坚,Stream串行迭代性能更差祈远,但多核情況下Stream迭代時(shí)性能較好呆万。
實(shí)驗(yàn)二 對(duì)象迭代
再來(lái)看對(duì)象的迭代效果。
測(cè)試內(nèi)容:找出字符串列表中最小的元素(自然順序)车份,對(duì)比f(wàn)or循環(huán)外部迭代和Stream API內(nèi)部迭代性能谋减。
測(cè)試程序StringTest
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee/StringTest.java
對(duì) Stream 不熟悉的,可以關(guān)注微信公眾號(hào):Java技術(shù)棧躬充,在后臺(tái)回復(fù):Java逃顶。
測(cè)試結(jié)果如下圖:
結(jié)果分析如下:
1、對(duì)于對(duì)象類型Stream串行迭代的性能開銷仍然高于外部迭代開銷(1.5倍)充甚,但差距沒(méi)有基本類型那么大以政。
2、Stream并行迭代的性能比串行迭代和外部迭代都好伴找。
再來(lái)單獨(dú)考察Stream并行迭代效果:
分析盈蛮,對(duì)于對(duì)象類型:
1、使用Stream并行API在單核情況下性能比f(wàn)or循環(huán)外部迭代差技矮;
2抖誉、隨著使用核數(shù)的增加,Stream并行效果逐漸變好衰倦,多核帶來(lái)的效果明顯袒炉。
以上兩個(gè)測(cè)試說(shuō)明,對(duì)于對(duì)象類型的簡(jiǎn)單迭代樊零,Stream串行迭代性能更差我磁,但多核情況下Stream迭代時(shí)性能較好。
實(shí)驗(yàn)三 復(fù)雜對(duì)象歸約
從實(shí)驗(yàn)一驻襟、二的結(jié)果來(lái)看夺艰,Stream串行執(zhí)行的效果都比外部迭代差(很多),是不是說(shuō)明Stream真的不行了沉衣?先別下結(jié)論郁副,我們?cè)賮?lái)考察一下更復(fù)雜的操作。
測(cè)試內(nèi)容:給定訂單列表豌习,統(tǒng)計(jì)每個(gè)用戶的總交易額存谎。對(duì)比使用外部迭代手動(dòng)實(shí)現(xiàn)和Stream API之間的性能。
我們將訂單簡(jiǎn)化為 <userName,price,timeStamp>構(gòu)成的元組肥隆,并用 Order對(duì)象來(lái)表示愕贡。測(cè)試程序ReductionTest
https://github.com/CarpenterLee/JavaLambdaInternals/blob/master/perf/StreamBenchmark/src/lee/ReductionTest.java
測(cè)試結(jié)果如下圖:
分析,對(duì)于復(fù)雜的歸約操作:
1巷屿、Stream API的性能普遍好于外部手動(dòng)迭代固以,并行Stream效果更佳;
再來(lái)考察并行度對(duì)并行效果的影響,測(cè)試結(jié)果如下:
分析憨琳,對(duì)于復(fù)雜的歸約操作:
1诫钓、使用Stream并行歸約在單核情況下性能比串行歸約以及手動(dòng)歸約都要差,簡(jiǎn)單說(shuō)就是最差的篙螟;
2菌湃、隨著使用核數(shù)的增加,Stream并行效果逐漸變好遍略,多核帶來(lái)的效果明顯惧所。
以上兩個(gè)實(shí)驗(yàn)說(shuō)明,對(duì)于復(fù)雜的歸約操作绪杏,Stream串行歸約效果好于手動(dòng)歸約下愈,在多核情況下,并行歸約效果更佳蕾久。我們有理由相信势似,對(duì)于其他復(fù)雜的操作,Stream API也能表現(xiàn)出相似的性能表現(xiàn)僧著。
結(jié)論
上述三個(gè)實(shí)驗(yàn)的結(jié)果可以總結(jié)如下:
1履因、對(duì)于簡(jiǎn)單操作,比如最簡(jiǎn)單的遍歷盹愚,Stream串行API性能明顯差于顯示迭代栅迄,但并行的Stream API能夠發(fā)揮多核特性。
2皆怕、對(duì)于復(fù)雜操作毅舆,Stream串行API性能可以和手動(dòng)實(shí)現(xiàn)的效果匹敵,在并行執(zhí)行時(shí)Stream API效果遠(yuǎn)超手動(dòng)實(shí)現(xiàn)端逼。
所以,如果出于性能考慮
1污淋、對(duì)于簡(jiǎn)單操作推薦使用外部迭代手動(dòng)實(shí)現(xiàn)
2顶滩、對(duì)于復(fù)雜操作,推薦使用Stream API
3寸爆、在多核情況下礁鲁,推薦使用并行Stream API來(lái)發(fā)揮多核優(yōu)勢(shì),
4赁豆、單核情況下不建議使用并行Stream API仅醇。
如果出于代碼簡(jiǎn)潔性考慮,使用Stream API能夠?qū)懗龈痰拇a魔种。即使是從性能方面說(shuō)析二,盡可能的使用Stream API也另外一個(gè)優(yōu)勢(shì),那就是只要Java Stream類庫(kù)做了升級(jí)優(yōu)化,代碼不用做任何修改就能享受到升級(jí)帶來(lái)的好處叶摄。