java理論基礎(chǔ)Stream性能論證測(cè)試示例
一、粉絲的反饋
問(wèn):stream比f(wàn)or循環(huán)慢5倍,用這個(gè)是為了啥? 答:互聯(lián)網(wǎng)是一個(gè)新聞泛濫的時(shí)代,三人成虎,以假亂真的事情時(shí)候發(fā)生。作為一個(gè)技術(shù)開(kāi)發(fā)者,要自己去動(dòng)手去做,不要人云亦云。
的確,這位粉絲說(shuō)的這篇文章我也看過(guò),我就不貼地址了,也沒(méi)必要給他帶流量。怎么說(shuō)呢?就是一個(gè)不懂得測(cè)試的、不入流開(kāi)發(fā)工程師做的性能測(cè)試,給出了一個(gè)危言聳聽(tīng)的結(jié)論。
二、所有性能測(cè)試結(jié)論都是片面的
性能測(cè)試是必要的,但針對(duì)性能測(cè)試的結(jié)果,永遠(yuǎn)要持懷疑態(tài)度。為什么這么說(shuō)?
- 性能測(cè)試脫離業(yè)務(wù)場(chǎng)景就是片面的性能測(cè)試。你能覆蓋所有的業(yè)務(wù)場(chǎng)景么?
- 性能測(cè)試脫離硬件環(huán)境就是片面的性能測(cè)試。你能覆蓋所有的硬件環(huán)境么?
- 性能測(cè)試脫離開(kāi)發(fā)人員的知識(shí)面就是片面的性能測(cè)試。你能覆蓋各種開(kāi)發(fā)人員奇奇怪怪的代碼么?
所以,我從來(lái)不相信網(wǎng)上的任何性能測(cè)試的文章。凡是我自己的從事的業(yè)務(wù)場(chǎng)景,我都要在接近生產(chǎn)環(huán)境的機(jī)器上自己測(cè)試一遍。 所有性能測(cè)試結(jié)論都是片面的,只有你生產(chǎn)環(huán)境下的運(yùn)行結(jié)果才是真的。
三、動(dòng)手測(cè)試Stream的性能
3.1.環(huán)境
windows10 、16G內(nèi)存、i7-7700HQ 2.8HZ 、64位操作系統(tǒng)、JDK 1.8.0_171
3.2.測(cè)試用例與測(cè)試結(jié)論
我們?cè)谏弦还?jié),已經(jīng)講過(guò):
- 針對(duì)不同的數(shù)據(jù)結(jié)構(gòu),Stream流的執(zhí)行效率是不一樣的
- 針對(duì)不同的數(shù)據(jù)源,Stream流的執(zhí)行效率也是不一樣的
所以記住筆者的話(huà):所有性能測(cè)試結(jié)論都是片面的,你要自己動(dòng)手做,相信你自己的代碼和你的環(huán)境下的測(cè)試!我的測(cè)試結(jié)果僅僅代表我自己的測(cè)試用例和測(cè)試數(shù)據(jù)結(jié)構(gòu)!
3.2.1.測(cè)試用例一
測(cè)試用例:5億個(gè)int隨機(jī)數(shù),求最小值 測(cè)試結(jié)論(測(cè)試代碼見(jiàn)后文):
- 使用普通for循環(huán),執(zhí)行效率是Stream串行流的2倍。也就是說(shuō)普通for循環(huán)性能更好。
- Stream并行流計(jì)算是普通for循環(huán)執(zhí)行效率的4-5倍。
- Stream并行流計(jì)算 > 普通for循環(huán) > Stream串行流計(jì)算
3.2.2測(cè)試用例二
測(cè)試用例:長(zhǎng)度為10的1000000隨機(jī)字符串,求最小值 測(cè)試結(jié)論(測(cè)試代碼見(jiàn)后文):
- 普通for循環(huán)執(zhí)行效率與Stream串行流不相上下
- Stream并行流的執(zhí)行效率遠(yuǎn)高于普通for循環(huán)
- Stream并行流計(jì)算 > 普通for循環(huán) = Stream串行流計(jì)算
3.2.3測(cè)試用例三
測(cè)試用例:10個(gè)用戶(hù),每人200個(gè)訂單。按用戶(hù)統(tǒng)計(jì)訂單的總價(jià)。 測(cè)試結(jié)論(測(cè)試代碼見(jiàn)后文):
- Stream并行流的執(zhí)行效率遠(yuǎn)高于普通for循環(huán)
- Stream串行流的執(zhí)行效率大于等于普通for循環(huán)
- Stream并行流計(jì)算 > Stream串行流計(jì)算 >= 普通for循環(huán)
四、最終測(cè)試結(jié)論
對(duì)于簡(jiǎn)單的數(shù)字(list-Int)遍歷,普通for循環(huán)效率的確比Stream串行流執(zhí)行效率高(1.5-2.5倍)。但是Stream流可以利用并行執(zhí)行的方式發(fā)揮CPU的多核優(yōu)勢(shì),因此并行流計(jì)算執(zhí)行效率高于for循環(huán)。
對(duì)于list-Object類(lèi)型的數(shù)據(jù)遍歷,普通for循環(huán)和Stream串行流比也沒(méi)有任何優(yōu)勢(shì)可言,更不用提Stream并行流計(jì)算。
雖然在不同的場(chǎng)景、不同的數(shù)據(jù)結(jié)構(gòu)、不同的硬件環(huán)境下。Stream流與for循環(huán)性能測(cè)試結(jié)果差異較大,甚至發(fā)生逆轉(zhuǎn)。但是總體上而言:
- Stream并行流計(jì)算 >> 普通for循環(huán) ~= Stream串行流計(jì)算 (之所以用兩個(gè)大于號(hào),你細(xì)品)
- 數(shù)據(jù)容量越大,Stream流的執(zhí)行效率越高。
- Stream并行流計(jì)算通常能夠比較好的利用CPU的多核優(yōu)勢(shì)。CPU核心越多,Stream并行流計(jì)算效率越高。
stream比f(wàn)or循環(huán)慢5倍?也許吧,單核CPU、串行Stream的int類(lèi)型數(shù)據(jù)遍歷?我沒(méi)試過(guò)這種場(chǎng)景,但是我知道這不是應(yīng)用系統(tǒng)的核心場(chǎng)景??戳耸畮灼獪y(cè)試博文,和我的測(cè)試結(jié)果。我的結(jié)論是: 在大多數(shù)的核心業(yè)務(wù)場(chǎng)景下及常用數(shù)據(jù)結(jié)構(gòu)下,Stream的執(zhí)行效率比f(wàn)or循環(huán)更高。 畢竟我們的業(yè)務(wù)中通常是實(shí)實(shí)在在的實(shí)體對(duì)象,沒(méi)事誰(shuí)總對(duì)List<Int>
類(lèi)型進(jìn)行遍歷?誰(shuí)的生產(chǎn)服務(wù)器是單核?。
五、測(cè)試代碼
<dependency> <groupId>com.github.houbb</groupId> <artifactId>junitperf</artifactId> <version>2.0.0</version> </dependency>
測(cè)試用例一:
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig; import com.github.houbb.junitperf.core.report.impl.HtmlReporter; import org.junit.jupiter.api.BeforeAll; import java.util.Arrays; import java.util.Random; public class StreamIntTest { public static int[] arr; @BeforeAll public static void init() { arr = new int[500000000]; //5億個(gè)隨機(jī)Int randomInt(arr); } @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class}) public void testIntFor() { minIntFor(arr); } @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class}) public void testIntParallelStream() { minIntParallelStream(arr); } @JunitPerfConfig( warmUp = 1000, reporter = {HtmlReporter.class}) public void testIntStream() { minIntStream(arr); } private int minIntStream(int[] arr) { return Arrays.stream(arr).min().getAsInt(); } private int minIntParallelStream(int[] arr) { return Arrays.stream(arr).parallel().min().getAsInt(); } private int minIntFor(int[] arr) { int min = Integer.MAX_VALUE; for (int anArr : arr) { if (anArr < min) { min = anArr; } } return min; } private static void randomInt(int[] arr) { Random r = new Random(); for (int i = 0; i < arr.length; i++) { arr[i] = r.nextInt(); } } }
測(cè)試用例二:
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig; import com.github.houbb.junitperf.core.report.impl.HtmlReporter; import org.junit.jupiter.api.BeforeAll; import java.util.ArrayList; import java.util.Random; public class StreamStringTest { public static ArrayList<String> list; @BeforeAll public static void init() { list = randomStringList(1000000); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testMinStringForLoop(){ String minStr = null; boolean first = true; for(String str : list){ if(first){ first = false; minStr = str; } if(minStr.compareTo(str)>0){ minStr = str; } } } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void textMinStringStream(){ list.stream().min(String::compareTo).get(); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testMinStringParallelStream(){ list.stream().parallel().min(String::compareTo).get(); } private static ArrayList<String> randomStringList(int listLength){ ArrayList<String> list = new ArrayList<>(listLength); Random rand = new Random(); int strLength = 10; StringBuilder buf = new StringBuilder(strLength); for(int i=0; i<listLength; i++){ buf.delete(0, buf.length()); for(int j=0; j<strLength; j++){ buf.append((char)('a'+ rand.nextInt(26))); } list.add(buf.toString()); } return list; } }
測(cè)試用例三:
import com.github.houbb.junitperf.core.annotation.JunitPerfConfig; import com.github.houbb.junitperf.core.report.impl.HtmlReporter; import org.junit.jupiter.api.BeforeAll; import java.util.*; import java.util.stream.Collectors; public class StreamObjectTest { public static List<Order> orders; @BeforeAll public static void init() { orders = Order.genOrders(10); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testSumOrderForLoop(){ Map<String, Double> map = new HashMap<>(); for(Order od : orders){ String userName = od.getUserName(); Double v; if((v=map.get(userName)) != null){ map.put(userName, v+od.getPrice()); }else{ map.put(userName, od.getPrice()); } } } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testSumOrderStream(){ orders.stream().collect( Collectors.groupingBy(Order::getUserName, Collectors.summingDouble(Order::getPrice))); } @JunitPerfConfig(duration = 10000, warmUp = 1000, reporter = {HtmlReporter.class}) public void testSumOrderParallelStream(){ orders.parallelStream().collect( Collectors.groupingBy(Order::getUserName, Collectors.summingDouble(Order::getPrice))); } } class Order{ private String userName; private double price; private long timestamp; public Order(String userName, double price, long timestamp) { this.userName = userName; this.price = price; this.timestamp = timestamp; } public String getUserName() { return userName; } public double getPrice() { return price; } public long getTimestamp() { return timestamp; } public static List<Order> genOrders(int listLength){ ArrayList<Order> list = new ArrayList<>(listLength); Random rand = new Random(); int users = listLength/200;// 200 orders per user users = users==0 ? listLength : users; ArrayList<String> userNames = new ArrayList<>(users); for(int i=0; i<users; i++){ userNames.add(UUID.randomUUID().toString()); } for(int i=0; i<listLength; i++){ double price = rand.nextInt(1000); String userName = userNames.get(rand.nextInt(users)); list.add(new Order(userName, price, System.nanoTime())); } return list; } @Override public String toString(){ return userName + "::" + price; } }
以上就是java理論基礎(chǔ)Stream性能論證測(cè)試示例的詳細(xì)內(nèi)容,更多關(guān)于java Stream性能測(cè)試的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring注解方式無(wú)法掃描Service注解的解決
這篇文章主要介紹了Spring注解方式無(wú)法掃描Service注解的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10jdk中String類(lèi)設(shè)計(jì)成final的原由
為什么jdk中把 String 類(lèi)設(shè)計(jì)成final,主要是為了“ 效率 ”和“安全性”的緣故,若 String 允許被繼承, 由于它的高度被使用率, 可能會(huì)降低程序的性能,所以String被定義成final,需要了解的朋友可以參考下2013-01-01Spring?Boot各類(lèi)變量的使用小結(jié)
這篇文章主要介紹了Spring?Boot各類(lèi)變量的使用小結(jié),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01Java用 Rhino/Nashorn 代替第三方 JSON 轉(zhuǎn)換庫(kù)
本篇文章主要介紹了Java用 Rhino/Nashorn 代替第三方 JSON 轉(zhuǎn)換庫(kù),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05Java線(xiàn)程池ForkJoinPool(工作竊取算法)的使用
Fork就是把一個(gè)大任務(wù)切分為若干個(gè)子任務(wù)并行地執(zhí)行,Join就是合并這些子任務(wù)的執(zhí)行結(jié)果,最后得到這個(gè)大任務(wù)的結(jié)果。Fork/Join?框架使用的是工作竊取算法。本文主要介紹了ForkJoinPool的使用,需要的可以參考一下2022-11-11swagger文檔增強(qiáng)工具knife4j使用圖文詳解
這篇文章主要介紹了swagger文檔增強(qiáng)工具knife4j使用詳解,想要使用knife4j非常簡(jiǎn)單,只要在Springboot項(xiàng)目中引入knife4j的依賴(lài)即可,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08SpringBoot啟動(dòng)java.nio.charset.MalformedInputException: I
本文主要介紹了SpringBoot啟動(dòng)java.nio.charset.MalformedInputException: Input length = 1報(bào)錯(cuò)的解決方案2023-07-07java評(píng)論、回復(fù)功能設(shè)計(jì)與實(shí)現(xiàn)方法
很多項(xiàng)目或者系統(tǒng)都有評(píng)論或者回復(fù)的需求,但評(píng)論回復(fù)的實(shí)現(xiàn)往往都比較復(fù)雜,也不好實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于java評(píng)論、回復(fù)功能設(shè)計(jì)與實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2022-06-06