已經(jīng)對(duì) Stream API 的用法鼓吹夠多了,用起簡潔直觀,但性能到底怎么樣呢?會(huì)不會(huì)有很高的性能損失?
本節(jié)我們對(duì) Stream API 的性能一探究竟。
為保證測(cè)試結(jié)果真實(shí)可信,我們將 JVM 運(yùn)行在-server模式下,測(cè)試數(shù)據(jù)在 GB 量級(jí),測(cè)試機(jī)器采用常見的商用服務(wù)器,配置如下:
測(cè)試方法和測(cè)試數(shù)據(jù)
性能測(cè)試并不是容易的事,Java 性能測(cè)試更費(fèi)勁,因?yàn)樘摂M機(jī)對(duì)性能的影響很大,JVM 對(duì)性能的影響有兩方面:
GC 的影響。GC 的行為是 Java 中很不好控制的一塊,為增加確定性,我們手動(dòng)指定使用 CMS 收集器,并使用 10GB 固定大小的堆內(nèi)存。具體到 JVM 參數(shù)就是-XX:+UseConcMarkSweepGC-Xms10G-Xmx10G
JIT(Just-In-Time) 即時(shí)編譯技術(shù)。即時(shí)編譯技術(shù)會(huì)將熱點(diǎn)代碼在 JVM 運(yùn)行的過程中編譯成本地代碼,測(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è)試帶來的抖動(dòng),測(cè)試 4 次求出平均時(shí)間作為運(yùn)行時(shí)間。
實(shí)驗(yàn)一 基本類型迭代
測(cè)試內(nèi)容:找出整型數(shù)組中的最小值。對(duì)比 for 循環(huán)外部迭代和 Stream API 內(nèi)部迭代性能。
測(cè)試程序 IntTest,測(cè)試結(jié)果如下圖:
圖中展示的是 for 循環(huán)外部迭代耗時(shí)為基準(zhǔn)的時(shí)間比值。分析如下:
對(duì)于基本類型 Stream 串行迭代的性能開銷明顯高于外部迭代開銷(兩倍);
Stream 并行迭代的性能比串行迭代和外部迭代都好。
并行迭代性能跟可利用的核數(shù)有關(guān),上圖中的并行迭代使用了全部 12 個(gè)核,為考察使用核數(shù)對(duì)性能的影響,我們專門測(cè)試了不同核數(shù)下的 Stream 并行迭代效果:
分析,對(duì)于基本類型:
使用 Stream 并行 API 在單核情況下性能很差,比 Stream 串行 API 的性能還差;
隨著使用核數(shù)的增加,Stream 并行效果逐漸變好,比使用 for 循環(huán)外部迭代的性能還好。
以上兩個(gè)測(cè)試說明,對(duì)于基本類型的簡單迭代,Stream 串行迭代性能更差,但多核情況下 Stream 迭代時(shí)性能較好。
實(shí)驗(yàn)二 對(duì)象迭代
再來看對(duì)象的迭代效果。
測(cè)試內(nèi)容:找出字符串列表中最小的元素(自然順序),對(duì)比 for 循環(huán)外部迭代和 Stream API 內(nèi)部迭代性能。
測(cè)試程序 StringTest,測(cè)試結(jié)果如下圖:
結(jié)果分析如下:
對(duì)于對(duì)象類型 Stream 串行迭代的性能開銷仍然高于外部迭代開銷(1.5 倍),但差距沒有基本類型那么大。
Stream 并行迭代的性能比串行迭代和外部迭代都好。
再來單獨(dú)考察 Stream 并行迭代效果:
分析,對(duì)于對(duì)象類型:
使用 Stream 并行 API 在單核情況下性能比 for 循環(huán)外部迭代差;
隨著使用核數(shù)的增加,Stream 并行效果逐漸變好,多核帶來的效果明顯。
以上兩個(gè)測(cè)試說明,對(duì)于對(duì)象類型的簡單迭代,Stream 串行迭代性能更差,但多核情況下 Stream 迭代時(shí)性能較好。
實(shí)驗(yàn)三 復(fù)雜對(duì)象歸約
從實(shí)驗(yàn)一、二的結(jié)果來看,Stream 串行執(zhí)行的效果都比外部迭代差(很多),是不是說明 Stream 真的不行了?先別下結(jié)論,我們?cè)賮砜疾煲幌赂鼜?fù)雜的操作。
測(cè)試內(nèi)容:給定訂單列表,統(tǒng)計(jì)每個(gè)用戶的總交易額。對(duì)比使用外部迭代手動(dòng)實(shí)現(xiàn)和 Stream API 之間的性能。
我們將訂單簡化為
分析,對(duì)于復(fù)雜的歸約操作:
Stream API 的性能普遍好于外部手動(dòng)迭代,并行 Stream 效果更佳;
再來考察并行度對(duì)并行效果的影響,測(cè)試結(jié)果如下:
分析,對(duì)于復(fù)雜的歸約操作:
使用 Stream 并行歸約在單核情況下性能比串行歸約以及手動(dòng)歸約都要差,簡單說就是最差的;
隨著使用核數(shù)的增加,Stream 并行效果逐漸變好,多核帶來的效果明顯。
以上兩個(gè)實(shí)驗(yàn)說明,對(duì)于復(fù)雜的歸約操作,Stream 串行歸約效果好于手動(dòng)歸約,在多核情況下,并行歸約效果更佳。我們有理由相信,對(duì)于其他復(fù)雜的操作,Stream API 也能表現(xiàn)出相似的性能表現(xiàn)。
結(jié)論
上述三個(gè)實(shí)驗(yàn)的結(jié)果可以總結(jié)如下:
對(duì)于簡單操作,比如最簡單的遍歷,Stream 串行 API 性能明顯差于顯示迭代,但并行的 Stream API 能夠發(fā)揮多核特性。
對(duì)于復(fù)雜操作,Stream 串行 API 性能可以和手動(dòng)實(shí)現(xiàn)的效果匹敵,在并行執(zhí)行時(shí) Stream API 效果遠(yuǎn)超手動(dòng)實(shí)現(xiàn)。
所以,如果出于性能考慮,
對(duì)于簡單操作推薦使用外部迭代手動(dòng)實(shí)現(xiàn),
對(duì)于復(fù)雜操作,推薦使用 Stream API,
在多核情況下,推薦使用并行 Stream API 來發(fā)揮多核優(yōu)勢(shì),
單核情況下不建議使用并行 Stream API。
如果出于代碼簡潔性考慮,使用 Stream API 能夠?qū)懗龈痰拇a。即使是從性能方面說,盡可能的使用 Stream API 也另外一個(gè)優(yōu)勢(shì),那就是只要 Java Stream 類庫做了升級(jí)優(yōu)化,代碼不用做任何修改就能享受到升級(jí)帶來的好處。
-
JAVA
+關(guān)注
關(guān)注
19文章
2974瀏覽量
105141 -
API
+關(guān)注
關(guān)注
2文章
1510瀏覽量
62394
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論