Java8 流奇怪的行为
Java8 streams strange behavior
我正在对一些 Java8 流 API 片段进行基准测试,但我无法弄清楚这个片段发生了什么。
我正在考虑 ParallelStream
及其实际工作原理,并尝试对顺序处理和并行处理进行一些比较。
我创建了两种不同的方法,都在添加 32.768.000 BigDecimal
的同时进行了巨大的迭代,其中一种使用 ParallelStream
,另一种使用正常的顺序迭代。我以一个我知道无效的测试结束,但有些地方引起了我的注意。
方法有:
private static void sumWithParallelStream() {
BigDecimal[] list = new BigDecimal[32_768_000];
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 32_768_000; i++) {
list[i] = new BigDecimal(i);
}
total = Arrays.asList(list).parallelStream().reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Total: " + total);
}
private static void sequenceSum() {
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 32_768_000; i++) {
total = total.add(new BigDecimal(i));
}
System.out.println("Total: " + total);
}
输出为:
Total: 536870895616000
sumWithParallelStream(): 30502 ms
Total: 536870895616000
sequenceSum(): 271 ms
然后我尝试删除 ParallelStream
并检查它的实际影响:
private static void sumWithParallelStream() {
BigDecimal[] list = new BigDecimal[32_768_000];
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 32_768_000; i++) {
list[i] = new BigDecimal(i);
total = total.add(list[i]);
}
System.out.println("Total: " + total);
}
注意sequenceSum()
方法不变
令人惊讶的是,新的输出是:
Total: 536870895616000
sumWithParallelStream(): 13487 ms
Total: 536870895616000
sequenceSum(): 879 ms
我多次重复这些更改,添加和删除 parallelStream
调用,sequenceSum()
的结果是一致的,涉及 parallelStream
时约 200 毫秒,不涉及时约 800 毫秒.在不同的机器上测试,Windows 和 Ubuntu.
最后,我的两个问题是:
- 为什么在第一种方法上使用
parallelStream
会干扰第二种方法?
- 为什么将
BigDecimal
个实例存储在数组中会使第一个方法慢得多(800 毫秒到 13000 毫秒)?
在第一个示例中,您分配了一个包含 32,768,000 个元素的数组,然后对其进行流处理。不需要数组分配和内存获取,这可能是减慢方法速度的原因。
IntStream.range(0, limit).parallel()
.mapToObj(BigDecimal::new)
.reduce(BigDecimal.ZERO, BigDecimal::add);
正如@apangin 在评论中指出的那样,问题出在垃圾收集器上。
我添加了 -XX:+PrintGCDetails
命令行参数,可以为每个 GC 运行 启用执行时间打印。然后我可以确认,当使用 parallelStream
GC 花费更多时间到 运行,可能是因为 Streams API 初始化和预热消耗更多内存并留下一些垃圾需要收集。
我正在对一些 Java8 流 API 片段进行基准测试,但我无法弄清楚这个片段发生了什么。
我正在考虑 ParallelStream
及其实际工作原理,并尝试对顺序处理和并行处理进行一些比较。
我创建了两种不同的方法,都在添加 32.768.000 BigDecimal
的同时进行了巨大的迭代,其中一种使用 ParallelStream
,另一种使用正常的顺序迭代。我以一个我知道无效的测试结束,但有些地方引起了我的注意。
方法有:
private static void sumWithParallelStream() {
BigDecimal[] list = new BigDecimal[32_768_000];
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 32_768_000; i++) {
list[i] = new BigDecimal(i);
}
total = Arrays.asList(list).parallelStream().reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Total: " + total);
}
private static void sequenceSum() {
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 32_768_000; i++) {
total = total.add(new BigDecimal(i));
}
System.out.println("Total: " + total);
}
输出为:
Total: 536870895616000
sumWithParallelStream(): 30502 ms
Total: 536870895616000
sequenceSum(): 271 ms
然后我尝试删除 ParallelStream
并检查它的实际影响:
private static void sumWithParallelStream() {
BigDecimal[] list = new BigDecimal[32_768_000];
BigDecimal total = BigDecimal.ZERO;
for (int i = 0; i < 32_768_000; i++) {
list[i] = new BigDecimal(i);
total = total.add(list[i]);
}
System.out.println("Total: " + total);
}
注意sequenceSum()
方法不变
令人惊讶的是,新的输出是:
Total: 536870895616000
sumWithParallelStream(): 13487 ms
Total: 536870895616000
sequenceSum(): 879 ms
我多次重复这些更改,添加和删除 parallelStream
调用,sequenceSum()
的结果是一致的,涉及 parallelStream
时约 200 毫秒,不涉及时约 800 毫秒.在不同的机器上测试,Windows 和 Ubuntu.
最后,我的两个问题是:
- 为什么在第一种方法上使用
parallelStream
会干扰第二种方法? - 为什么将
BigDecimal
个实例存储在数组中会使第一个方法慢得多(800 毫秒到 13000 毫秒)?
在第一个示例中,您分配了一个包含 32,768,000 个元素的数组,然后对其进行流处理。不需要数组分配和内存获取,这可能是减慢方法速度的原因。
IntStream.range(0, limit).parallel()
.mapToObj(BigDecimal::new)
.reduce(BigDecimal.ZERO, BigDecimal::add);
正如@apangin 在评论中指出的那样,问题出在垃圾收集器上。
我添加了 -XX:+PrintGCDetails
命令行参数,可以为每个 GC 运行 启用执行时间打印。然后我可以确认,当使用 parallelStream
GC 花费更多时间到 运行,可能是因为 Streams API 初始化和预热消耗更多内存并留下一些垃圾需要收集。