Java Stream.reduce()和Stream.collect()之间惊人的性能差异
我想比较两个Java8流终端操作Java Stream.reduce()和Stream.collect()之间惊人的性能差异,java,performance,lambda,java-8,java-stream,Java,Performance,Lambda,Java 8,Java Stream,我想比较两个Java8流终端操作reduce()和collect()的并行性能 让我们看一下以下Java8并行流示例: import java.math.BigInteger; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; import static java
reduce()
和collect()
的并行性能
让我们看一下以下Java8并行流示例:
import java.math.BigInteger;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static java.math.BigInteger.ONE;
public class StartMe {
static Function<Long, BigInteger> fac;
static {
fac = x -> x==0? ONE : BigInteger.valueOf(x).multiply(fac.apply(x - 1));
}
static long N = 2000;
static Supplier<BigInteger[]> one() {
BigInteger[] result = new BigInteger[1];
result[0] = ONE;
return () -> result;
}
static BiConsumer<BigInteger[], ? super BigInteger> accumulator() {
return (BigInteger[] ba, BigInteger b) -> {
synchronized (fac) {
ba[0] = ba[0].multiply(b);
}
};
}
static BiConsumer<BigInteger[], BigInteger[]> combiner() {
return (BigInteger[] b1, BigInteger[] b2) -> {};
}
public static void main(String[] args) throws Exception {
long t0 = System.currentTimeMillis();
BigInteger result1 = Stream.iterate(ONE, x -> x.add(ONE)).parallel().limit(N).reduce(ONE, BigInteger::multiply);
long t1 = System.currentTimeMillis();
BigInteger[] result2 = Stream.iterate(ONE, x -> x.add(ONE)).parallel().limit(N).collect(one(), accumulator(), combiner());
long t2 = System.currentTimeMillis();
BigInteger result3 = fac.apply(N);
long t3 = System.currentTimeMillis();
System.out.println("reduce(): deltaT = " + (t1-t0) + "ms, result 1 = " + result1);
System.out.println("collect(): deltaT = " + (t2-t1) + "ms, result 2 = " + result2[0]);
System.out.println("recursive: deltaT = " + (t3-t2) + "ms, result 3 = " + result3);
}
}
一些评论:
- 我必须同步
,因为它并行访问同一个数组累加器()
- 我预计
和reduce()
将产生相同的性能,但collect()
比reduce()
慢约2倍,即使collect()
必须同步李>collect()
- 最快的算法是顺序和递归算法(这可能会显示并行流管理的巨大开销)
我没想到
reduce()
的性能会比collect()
的性能差。为什么会这样 基本上,您是在测量第一次执行的代码的初始开销。不仅优化器没有任何工作,您还可以测量加载、验证和初始化类的开销
因此,由于每次评估都可以重用已经为上一次评估加载的类,因此减少评估时间也就不足为奇了。在一个循环中运行所有三个评估,甚至只是更改顺序,都会给您一个完全不同的画面
唯一可预测的结果是,简单的递归求值将具有最小的初始开销,因为它不需要加载流
API类
如果您多次运行代码,或者更好地使用复杂的基准测试工具,我想您将得到与我类似的结果,
reduce
明显优于collect
,并且确实比单线程方法更快
收集速度较慢的原因是您完全错误地使用了它。将为每个线程查询供应商,以获得不同的容器,因此累加器函数不需要任何额外的同步。但重要的是,组合器函数要正确地将不同线程的结果容器连接到单个结果中
正确的做法是:
BigInteger[] result2 = Stream.iterate(ONE, x -> x.add(ONE)).parallel().limit(N)
.collect(()->new BigInteger[]{ONE},
(a,v)->a[0]=a[0].multiply(v), (a,b)->a[0]=a[0].multiply(b[0]));
在我的系统中,它的性能与<代码> Reals方法一致。由于使用数组作为可变容器不能改变
biginger
的不可变性质,因此在这里使用collect
没有任何优势,使用reduce
是直接的,正如所说,当两种方法正确使用时,具有同等的性能
顺便说一下,我不明白为什么这么多程序员试图创建自引用lambda表达式。递归函数的直接方法仍然是一种方法:
static BigInteger fac(long x) {
return x==0? ONE : BigInteger.valueOf(x).multiply(fac(x - 1));
}
static final Function<Long, BigInteger> fac=StartMe::fac;
@resueman我建议看看JMH框架。它有一个很好的教程。
static BigInteger fac(long x) {
return x==0? ONE : BigInteger.valueOf(x).multiply(fac(x - 1));
}
static final Function<Long, BigInteger> fac=StartMe::fac;
BigInteger result4 = LongStream.rangeClosed(1, N).parallel()
.mapToObj(BigInteger::valueOf).reduce(BigInteger::multiply).orElse(ONE);