Java 惰性评估
这是2021年Java日历 | Advent Calendar第16天的文章。
我会永远选择一个懒人来做一个困难的工作,因为他会找到一个简单的方法来完成它 – 比尔·盖茨
※ 因为懒惰的人能找到更简便的方式,所以我总是把困难的工作交给他们。- 比尔·盖茨
总结
我们来看一下通过Stream API执行Java的延迟评估(lazy evaluation)的性能。
如同使用Java Stream进行验证一样,我们将尝试处理1000个以上的对象。
我的观感是,总的来说,结果表明使用 Stream API 可以使堆内存和处理速度提升十倍以上。
当被称为Stream API的fliter(Predicate)被认为不属于延迟评估时…
相比较的代码
代码是从包含1000个(,1万个,10万个)适当对象的列表中提取满足某个条件(本例中为年龄大于等于30岁)的东西。
在Stream API的情况下
void useStream() {
l.stream().filter(p -> p.age >= 30).collect(Collectors.toList());
}
如果在 Stream API 中加上 parallel 标志
void useStreamWithParallel() {
l.stream().filter(p -> p.age >= 30).parallel().collect(Collectors.toList());
}
使用列表的情况下
void useList() {
List<Parson> res = new ArrayList<>();
for (var p : l) {
if (p.age < 30) {
continue;
}
res.add(create());
}
}
测量结果
在此次测试中,我们使用了之前在“Javaでヒープサイズ測定 JUnit5編”中介绍的quickperf和在“Javaでベンチマーク(性能測定) JUnit5編”中介绍的jmh。
我想推荐使用Stream API的parallel功能,但由于结果迟迟不出来,所以这次我贪心地进行了1万条和10万条的验证。
相比没有parallel的Stream API,得分在1万条时稍逊一筹,但在10万条时却反超了。
JVM 的堆大小
一千件
[QUICK PERF] Measured heap allocation (test method thread): 21.44 Kilo bytes (21 952 bytes) ← Stream API
[QUICK PERF] Measured heap allocation (test method thread): 50.95 Kilo bytes (52 176 bytes) ← Stream API (parallel)
[QUICK PERF] Measured heap allocation (test method thread): 264.87 Kilo bytes (271 224 bytes) ← List
一萬件 (yī
[QUICK PERF] Measured heap allocation (test method thread): 121.70 Kilo bytes (124 616 bytes) ← Stream API
[QUICK PERF] Measured heap allocation (test method thread): 160.49 Kilo bytes (164 344 bytes) ← Stream API (parallel)
[QUICK PERF] Measured heap allocation (test method thread): 2.45 Mega bytes (2 573 952 bytes) ← List (単位が違う)
一万件
[QUICK PERF] Measured heap allocation (test method thread): 845.55 Kilo bytes (865 848 bytes) ← Stream API
[QUICK PERF] Measured heap allocation (test method thread): 823.67 Kilo bytes (843 440 bytes) ← Stream API (parallel)
[QUICK PERF] Measured heap allocation (test method thread): 24.67 Mega bytes (25 870 512 bytes) ← List (単位が違う)
基准测试
一千件
Benchmark Mode Cnt Score Error Units
StreamVsListLazyPerfTest.useList thrpt 5 1171.093 ± 22.063 ops/s
StreamVsListLazyPerfTest.useStream thrpt 5 218394.756 ± 4049.119 ops/s
StreamVsListLazyPerfTest.useStreamWithParallel thrpt 5 68465.691 ± 1838.747 ops/s
一万件
Benchmark Mode Cnt Score Error Units
StreamVsListLazyPerfTest.useList thrpt 5 118.407 ± 3.344 ops/s
StreamVsListLazyPerfTest.useStream thrpt 5 21036.060 ± 379.085 ops/s
StreamVsListLazyPerfTest.useStreamWithParallel thrpt 5 18515.519 ± 3244.870 ops/s
10万件
Benchmark Mode Cnt Score Error Units
StreamVsListLazyPerfTest.useList thrpt 5 10.984 ± 0.102 ops/s
StreamVsListLazyPerfTest.useStream thrpt 5 813.617 ± 86.894 ops/s
StreamVsListLazyPerfTest.useStreamWithParallel thrpt 5 2140.738 ± 79.030 ops/s
追加文件
用于堆测量的测试代码
@QuickPerfTest
public class StreamVsListLazyTest {
List<Parson> l = new ArrayList<>();
@BeforeEach
void beforeAll() {
for (int i = 0; i < 10000; i++) {
l.add(create());
}
}
@MeasureHeapAllocation
@Test
void useStream() {
l.stream().filter(p -> p.age >= 30).collect(Collectors.toList());
}
@MeasureHeapAllocation
@Test
void useStreamWithParallel() {
l.stream().filter(p -> p.age >= 30).parallel().collect(Collectors.toList());
}
@MeasureHeapAllocation
@Test
void useList() {
List<Parson> res = new ArrayList<>();
for (var p : l) {
if (p.age < 30) {
continue;
}
res.add(create());
}
}
@AllArgsConstructor
@Data
class Parson {
private String name;
private int age;
private String addr;
private String addr2;
}
Parson create() {
return new Parson(RandomStringUtils.randomAlphabetic(10), new Random().nextInt(100), RandomStringUtils.randomAlphabetic(20), RandomStringUtils.randomAlphabetic(20));
}
}
用于获取基准测试的测试代码
@State(value = Scope.Benchmark)
public class StreamVsListLazyPerfTest {
List<Parson> l = new ArrayList<>();
@Setup
public void setup() {
for (int i = 0; i < 10000; i++) {
l.add(create());
}
}
@Benchmark
public void useStream() {
l.stream().filter(p -> p.age >= 30).collect(Collectors.toList());
}
@Benchmark
public void useStreamWithParallel() {
l.stream().filter(p -> p.age >= 30).parallel().collect(Collectors.toList());
}
@Benchmark
public void useList() {
List<Parson> res = new ArrayList<>();
for (var p : l) {
if (p.age < 30) {
continue;
}
res.add(create());
}
}
// Junit のテストアノテーションで Runner を設定する
@Test
void benchMark() throws RunnerException {
Options opt = new OptionsBuilder()
.include(StreamVsListLazyPerfTest.class.getSimpleName())
.forks(1) // 1回実行
.warmupIterations(1) // 1回繰り返し
.build();
new Runner(opt).run();
}
@AllArgsConstructor
@Data
class Parson {
private String name;
private int age;
private String addr;
private String addr2;
}
Parson create() {
return new Parson(RandomStringUtils.randomAlphabetic(10), new Random().nextInt(100), RandomStringUtils.randomAlphabetic(20), RandomStringUtils.randomAlphabetic(20));
}
}