Java Stream 效能測試
阿新 • • 發佈:2021-12-21
Java Stream 效能測試
本測試是測試 Java Stream 與傳統程式設計方式(即 foreach)之間的效能差距。
Java Stream 效能測試
本測試是測試 Java Stream 與傳統程式設計方式(即 foreach)之間的效能差距。
測試環境
JDK
JDK 1.8.0_191, Java HotSpot(TM) 64-Bit Server VM, 25.191-b12
測試框架
JMH v1.33
測試機器
- CPU:AMD Ryzen 7 3700X 8 核 16 執行緒 4.4GHz
- 記憶體:8GB * 2 1200MHz
- 作業系統:Windows 10 專業版 64 位 19043.1348
測試方式
- 用 maven 把專案打成 jar 包,在命令列介面執行這個 jar 包
- 每個測試專案預熱 3 次,每次持續 5 秒
- 每個測試專案執行 5 次,每次持續 5 秒
- 每個測試場景跑 3 輪,測試結果裡的是兩種方式每毫秒的吞吐量
執行命令
java -server -Xms2048m -jar stream-demo-1.0-SNAPSHOT-jar-with-dependencies.jar
測試場景
測試資料類:
public class User {
private Integer id;
private String username;
private Boolean sex;
private Integer type;
...
}
場景一
將使用者列表轉成 id -> username 的對映
測試程式碼
@BenchmarkMode(Mode.Throughput) @State(Scope.Thread) @Fork(1) @Warmup(iterations = 3, time = 5) @Measurement(iterations = 5, time = 5) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class PerformanceTest { private final List<User> users = initUsers(); /** * 傳統方式 */ @Benchmark public Map<Integer, String> tradition() { Map<Integer, String> map = new HashMap<>((int) (users.size() / 0.75)); for (User user : users) { map.put(user.getId(), user.getUsername()); } return map; } /** * Stream方式 */ @Benchmark public Map<Integer, String> stream() { Map<Integer, String> map = users.stream().collect(Collectors.toMap( User::getId, User::getUsername)); return map; } private List<User> initUsers() { int size = 1000; List<User> users = new ArrayList<>(); for (int i = 0; i < size; i++) { users.add(new User(i, "user" + i, true, 0)); } return users; } public static void main(String[] args) throws Exception { Options opt = new OptionsBuilder() .include(PerformanceTest.class.getSimpleName()) .build(); new Runner(opt).run(); } }
測試結果
第一輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 87.527 ± 6.281 ops/ms
PerformanceTest.stream thrpt 5 62.563 ± 0.199 ops/ms
第二輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 85.194 ± 2.157 ops/ms
PerformanceTest.stream thrpt 5 62.310 ± 0.203 ops/ms
第三輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 87.472 ± 4.789 ops/ms
PerformanceTest.stream thrpt 5 62.572 ± 2.242 ops/ms
傳統方式跟 Stream 方式的比值大約是 1.39
場景二
將使用者列表轉成 VO 列表
測試程式碼
@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 5)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class PerformanceTest {
private final List<User> users = initUsers();
/**
* 傳統方式
*/
@Benchmark
public List<UserVO> tradition() {
List<UserVO> userVOList = new ArrayList<>(users.size());
for (User user : users) {
userVOList.add(toUserVo(user));
}
return userVOList;
}
/**
* Stream方式
*/
@Benchmark
public List<UserVO> stream() {
List<UserVO> userVoList = users.stream()
.map(this::toUserVo)
.collect(Collectors.toList());
return userVoList;
}
private UserVO toUserVo(User user) {
UserVO userVO = new UserVO();
userVO.setId(user.getId());
userVO.setUsername(user.getUsername());
userVO.setSex(getSexStr(user.getSex()));
userVO.setType(getTypeStr(user.getType()));
return userVO;
}
private String getSexStr(Boolean sex) {
if (sex == null) return "";
return sex ? "男" : "女";
}
private String getTypeStr(Integer type) {
if (type == null) return "";
switch (type) {
case 0: return "青銅使用者";
case 1: return "白銀使用者";
case 2: return "黃金使用者";
default: return "";
}
}
private List<User> initUsers() {
int size = 1000;
List<User> users = new ArrayList<>();
for (int i = 0; i < size; i++) {
users.add(new User(i, "user" + i, true, 0));
}
return users;
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(PerformanceTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
測試結果
第一輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 126.362 ± 1.992 ops/ms
PerformanceTest.stream thrpt 5 86.086 ± 2.573 ops/ms
第二輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 124.212 ± 2.732 ops/ms
PerformanceTest.stream thrpt 5 81.427 ± 1.011 ops/ms
第三輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 124.594 ± 1.108 ops/ms
PerformanceTest.stream thrpt 5 85.825 ± 1.004 ops/ms
傳統方式跟 Stream 方式的比值大約是 1.48
場景三
將使用者列表的 username 按照在列表中的順序用逗號拼接起來,並去重
測試程式碼
@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 5)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class PerformanceTest {
private final List<User> users = initUsers();
/**
* 傳統方式
*/
@Benchmark
public String tradition() {
StringBuilder usernames = new StringBuilder();
Set<String> set = new HashSet<>();
for (User user : users) {
if (set.add(user.getUsername())) {
usernames.append(user.getUsername()).append(',');
}
}
if (usernames.length() != 0) {
usernames.deleteCharAt(usernames.length() - 1);
}
return usernames.toString();
}
/**
* Stream方式
*/
@Benchmark
public String stream() {
String usernames = users.stream()
.map(User::getUsername)
.distinct()
.collect(Collectors.joining(","));
return usernames;
}
private List<User> initUsers() {
int size = 1000;
List<User> users = new ArrayList<>();
for (int i = 0; i < size; i++) {
users.add(new User(i, "user" + i, true, 0));
}
return users;
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(PerformanceTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
測試結果
第一輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 35.034 ± 0.502 ops/ms
PerformanceTest.stream thrpt 5 26.225 ± 0.542 ops/ms
第二輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 35.071 ± 0.274 ops/ms
PerformanceTest.stream thrpt 5 26.634 ± 0.304 ops/ms
第三輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 34.946 ± 0.318 ops/ms
PerformanceTest.stream thrpt 5 26.140 ± 0.167 ops/ms
傳統方式跟 Stream 方式的比值大約是 1.33
場景四
統計整型陣列中的偶數個數
測試程式碼
@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 5)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class PerformanceTest {
private final int[] array = initArray();
/**
* 傳統方式
*/
@Benchmark
public int tradition() {
int count = 0;
for (int i : array) {
if (i % 2 == 0) {
count++;
}
}
return count;
}
/**
* Stream方式
*/
@Benchmark
public int stream() {
long count = Arrays.stream(array)
.filter(i -> i % 2 == 0)
.count();
return (int) count;
}
private int[] initArray() {
int size = 1000;
int[] array = new int[size];
for (int i = 0; i < size; i++) {
array[i] = i;
}
return array;
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(PerformanceTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
測試結果
第一輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 2099.627 ± 24.871 ops/ms
PerformanceTest.stream thrpt 5 1203.562 ± 12.984 ops/ms
第二輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 2083.610 ± 6.045 ops/ms
PerformanceTest.stream thrpt 5 1220.351 ± 7.516 ops/ms
第三輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 2096.514 ± 58.327 ops/ms
PerformanceTest.stream thrpt 5 1234.086 ± 14.287 ops/ms
傳統方式跟 Stream 方式的比值大約是 1.72
場景五
跟場景一一樣,不過使用的是並行流
測試程式碼
@BenchmarkMode(Mode.Throughput)
@State(Scope.Thread)
@Fork(1)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 5)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class PerformanceTest {
private final List<User> users = initUsers();
/**
* 傳統方式
*/
@Benchmark
public Map<Integer, String> tradition() {
Map<Integer, String> map = new HashMap<>((int) (users.size() / 0.75));
for (User user : users) {
map.put(user.getId(), user.getUsername());
}
return map;
}
/**
* Stream方式
*/
@Benchmark
public Map<Integer, String> stream() {
Map<Integer, String> map = users.parallelStream()
.collect(Collectors.toMap(
User::getId, User::getUsername));
return map;
}
private List<User> initUsers() {
int size = 1000;
List<User> users = new ArrayList<>();
for (int i = 0; i < size; i++) {
users.add(new User(i, "user" + i, true, 0));
}
return users;
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(PerformanceTest.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
測試結果
第一輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 81.262 ± 2.309 ops/ms
PerformanceTest.stream thrpt 5 17.596 ± 0.013 ops/ms
第二輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 85.698 ± 3.744 ops/ms
PerformanceTest.stream thrpt 5 17.588 ± 0.092 ops/ms
第三輪
Benchmark Mode Cnt Score Error Units
PerformanceTest.tradition thrpt 5 86.445 ± 6.377 ops/ms
PerformanceTest.stream thrpt 5 17.668 ± 0.021 ops/ms
傳統方式跟 Stream 方式的比值大約是 4.79
總結
- 對於一般場景下 1000 左右的資料量的處理,Stream 方式的耗時大概是傳統方式的 1.5 倍左右
- 對於基本資料型別 1000 左右資料量的處理,Stream 方式的耗時大概是傳統方式在 1.7 倍左右
- 對於計算量較少的情況,不建議使用並行流
使用 Stream 方式跟傳統程式設計方式相比執行效率之間的差距並不大,除非是對效能要求很高的系統,否則對於一般的業務系統來說不會造成 CPU 的瓶頸,IO 才是最大的瓶頸。相比之下,使用 Stream 可以讓程式碼變得更加乾淨整潔,可讀性和可維護性更好,所以一般情況下推薦使用 Stream 來程式設計。