java – Stream.reduce()和Stream.collect()之间令人惊讶的性能差异
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了java – Stream.reduce()和Stream.collect()之间令人惊讶的性能差异,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含3678字,纯文字阅读大概需要6分钟。
内容图文
![java – Stream.reduce()和Stream.collect()之间令人惊讶的性能差异](/upload/InfoBanner/zyjiaocheng/750/38dcbf12c55b41fea55d2b4d82766e37.jpg)
我想比较两个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.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);
}
}
它计算n!使用一些 – 不可否认奇怪;-) – 算法.
然而,性能结果令人惊讶:
reduce(): deltaT = 44ms, result 1 = 3316275...
collect(): deltaT = 22ms, result 2 = 3316275...
recursive: deltaT = 11ms, result 3 = 3316275...
一些评论:
>我必须同步accumulator()因为它并行访问同一个数组.
>我期望reduce()和collect()会产生相同的性能,但reduce()比collect()慢?2倍,即使必须同步collect()!
>最快的算法是顺序和递归算法(可能显示并行流管理的巨大开销)
我没想到reduce()的性能会比collect()的性能差.为什么会这样?
解决方法:
基本上,您正在测量第一次执行的代码的初始开销.不仅优化器还没有任何工作,您正在测量加载,验证和初始化类的开销.
所以毫无疑问,评估时间会减少,因为每个评估都可以重复使用已经为之前评估加载的类.在一个循环中运行所有三个评估甚至只是更改顺序将给你一个完全不同的图片.
唯一可预测的结果是简单的递归评估将具有最小的初始开销,因为它不需要加载Stream 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]));
在我的系统上,它的性能与reduce方法相当.由于使用数组作为可变容器不能改变BigInteger的不可变特性,因此在这里使用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;
(虽然在你的代码中,你根本不需要函数< Long,BigInteger>,只需直接调用fac(long)).
最后一点,Stream.iterate和Stream.limit都非常不适合并行执行.使用具有可预测大小和独立操作的流将显着优于您的解决方案:
BigInteger result4 = LongStream.rangeClosed(1, N).parallel()
.mapToObj(BigInteger::valueOf).reduce(BigInteger::multiply).orElse(ONE);
内容总结
以上是互联网集市为您收集整理的java – Stream.reduce()和Stream.collect()之间令人惊讶的性能差异全部内容,希望文章能够帮你解决java – Stream.reduce()和Stream.collect()之间令人惊讶的性能差异所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。