当前位置: 首页 > 科技观察

Java8中优雅的Stream在性能上“优雅”吗?

时间:2023-03-12 07:14:25 科技观察

在上一篇文章中,我们介绍了Java8中Stream相关的API。我们提到StreamAPI可以大大提高Java程序员的生产力,让程序员写出高效、干净、简洁的代码。那么,StreamAPI的性能如何呢?整洁的代码是否意味着性能损失?在本文中,我们将仔细研究StreamAPI的性能。为了保证测试结果的真实性,我们以-server模式运行JVM,测试数据为GB量级。测试机采用普通商用服务器,配置如下:OSCentOS6.7x86_64CPUIntelXeonX5675,12MCache3.06GHz,6Cores12ThreadsMemory96GBJDKjavaversion1.8.0_91,JavaHotSpot(TM)64-BitServerVM1.测试方法和数据性能测试不容易,Java性能测试比较费力,因为虚拟机对性能的影响很大,JVM对性能的影响有两个方面:1.GC的影响.GC的行为在Java中很难控制。为了增加确定性,我们手动指定使用CMS收集器并使用10GB的固定大小的堆内存。具体到JVM参数是-XX:+UseConcMarkSweepGC-Xms10G-Xmx10G2.JIT(Just-In-Time)即时编译技术。Just-in-time编译技术是在JVM运行过程中将热点代码编译成本地代码。测试时,我们会先预热程序,触发测试函数的即时编译。相关的JVM参数是-XX:CompileThreshold=10000。ForkJoinPool.commonPool()得到的线程池在Stream并行执行时使用。为了控制并行度,我们使用Linuxtaskset命令来指定JVM可用的内核数。测试数据由程序随机生成。为了防止一次测试造成的抖动,测试4次计算平均时间作为运行时间。2.基本类型迭代测试内容:在整型数组中寻找最小值。比较for循环的外部迭代和StreamAPI的内部迭代的性能。测试程序IntTest,测试结果如下图所示:图中以for循环外部迭代时间的时间比例为基准。分析如下:对于基本类型Stream,串行迭代的性能开销明显高于外部迭代(两倍);Stream并行迭代的性能优于串行迭代和外部迭代。并行迭代的性能与可用核数有关。上图中的并行迭代使用了所有12个核心。为了考察使用核数对性能的影响,我们专门测试了不同核数下Stream并行迭代的效果:分析,对于基本类型:使用Stream并行API的情况下性能很差单核的,比StreamserialAPI的性能差;随着使用核数的增加,Stream并行效果逐渐变好,比使用for循环的外部迭代fine的性能要好。上面两个测试表明,对于基本类型的简单迭代,Stream串行迭代的性能更差,但是在多核的情况下Stream迭代的性能更好。2.对象迭代接下来我们看一下对象的迭代效果。测试内容:查找字符串列表中的最小元素(自然顺序),比较for循环外部迭代和StreamAPI内部迭代的性能。测试程序StringTest,测试结果如下图:结果分析如下:对象类型Stream串行迭代的性能开销仍然高于外部迭代开销(1.5倍),但是差距没有基本型那么大。Stream并行迭代的性能优于串行迭代和外部迭代。单独考察Stream并行迭代的效果:分析,针对对象类型:单核情况下使用Stream并行API的性能比for循环的外部迭代要差;随着使用核心数的增加,Stream并行效果逐渐提升,多核带来的效果明显。以上两个测试表明,对于对象类型的简单迭代,Stream串行迭代的性能更差,但在多核的情况下Stream迭代的性能更好。3、复杂对象缩减从实验1和2的结果来看,Stream串行执行的效果比外部迭代差很多(很多),是不是说明Stream真的不行了?不要急于下结论,让我们检查更复杂的操作。测试内容:给定一个订单列表,统计每个用户的总交易金额。比较使用外部迭代和StreamAPI的手动实现之间的性能。我们将订单简化为一个由组成的元组,并使用Order对象来表示。测试程序ReductionTest,测试结果如下:分析,对于复杂的归约操作:StreamAPI的性能普遍优于外部手动迭代,并行Stream的效果更好;我们来看看并行度对并行效果的影响,测试结果如下:分析,对于复杂的归约操作:在单核的情况下,使用Stream并行归约的性能比串行归约和手动归约差,这简直是最糟糕的;随着使用的核数增加,Stream的并行效果逐渐变好,多核效果明显。以上两个实验表明,对于复杂的归约操作,Stream串行归约优于手动归约,多核情况下,并行归约效果更好。我们有理由相信,对于其他复杂的操作,StreamAPI也能表现出类似的性能。2.结论以上三个实验的结果可以总结如下:对于简单的操作,比如最简单的遍历,Stream串行API的性能明显比显示迭代差,但是并行StreamAPI可以发挥多-核心功能。对于复杂的操作,Stream串行API的性能可以媲美手工实现,StreamAPI在并行执行时性能远超手工实现。因此,出于性能的考虑,1.对于简单的操作,建议使用外部迭代来手动实现。2.对于复杂的操作,推荐使用StreamAPI。3.在多核的情况下,推荐使用parallelStreamAPI来发挥多核的优势。4.单核在某些情况下,不推荐使用并行StreamAPI。为了代码简洁,使用StreamAPI可以写出更短的代码。即使在性能方面,尽可能使用StreamAPI还有一个好处,那就是只要对JavaStream类库进行升级和优化,代码无需任何修改就可以享受升级带来的好处。