Java 8中用法优雅的Stream,性能也"优雅"吗?

之前的文章中我们介绍了Java 8中Stream相关的API,我们提到Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

那么,Stream API的性能到底如何呢,代码整洁的背后是否意味着性能的损耗呢?本文我们对Stream API的性能一探究竟。

为保证测试结果真实可信,我们将JVM运行在-server模式下,测试数据在GB量级,测试机器采用常见的商用服务器,配置如下:

一、测试方法与数据

性能测试并不是容易的事,Java性能测试更费劲,因为虚拟机对性能的影响很大,JVM对性能的影响有两方面:

  1. GC的影响。GC的行为是Java中很不好控制的一块,为增加确定性,我们手动指定使用CMS收集器,并使用10GB固定大小的堆内存。具体到JVM参数就是-XX:+UseConcMarkSweepGC -Xms10G -Xmx10G

  2. JIT(Just-In-Time)即时编译技术。即时编译技术会将热点代码在JVM运行的过程中编译成本地代码,测试时我们会先对程序预热,触发对测试函数的即时编译。相关的JVM参数是-XX:CompileThreshold=10000。

Stream并行执行时用到ForkJoinPool.commonPool()得到的线程池,为控制并行度我们使用Linux的taskset命令指定JVM可用的核数。

测试数据由程序随机生成。为防止一次测试带来的抖动,测试4次求出平均时间作为运行时间。

二、基本类型迭代

测试内容:找出整型数组中的最小值。对比for循环外部迭代和Stream API内部迭代性能。

测试程序IntTest,测试结果如下图:

图中展示的是for循环外部迭代耗时为基准的时间比值。分析如下:

  1. 对于基本类型Stream串行迭代的性能开销明显高于外部迭代开销(两倍);

  2. Stream并行迭代的性能比串行迭代和外部迭代都好。

并行迭代性能跟可利用的核数有关,上图中的并行迭代使用了全部12个核,为考察使用核数对性能的影响,我们专门测试了不同核数下的Stream并行迭代效果:

分析,对于基本类型:

  1. 使用Stream并行API在单核情况下性能很差,比Stream串行API的性能还差;

  2. 随着使用核数的增加,Stream并行效果逐渐变好,比使用for循环外部迭代的性能还好。

以上两个测试说明,对于基本类型的简单迭代,Stream串行迭代性能更差,但多核情况下Stream迭代时性能较好。

三、对象迭代

接下来我们再来看对象的迭代效果。

测试内容:找出字符串列表中最小的元素(自然顺序),对比for循环外部迭代和Stream API内部迭代性能。

测试程序StringTest,测试结果如下图:

结果分析如下:

  1. 对于对象类型Stream串行迭代的性能开销仍然高于外部迭代开销(1.5倍),但差距没有基本类型那么大。

  2. Stream并行迭代的性能比串行迭代和外部迭代都好。

再来单独考察Stream并行迭代效果:

分析,对于对象类型:

  1. 使用Stream并行API在单核情况下性能比for循环外部迭代差;

  2. 随着使用核数的增加,Stream并行效果逐渐变好,多核带来的效果明显。

以上两个测试说明,对于对象类型的简单迭代,Stream串行迭代性能更差,但多核情况下Stream迭代时性能较好。

四、复杂对象归约

从实验一、二的结果来看,Stream串行执行的效果都比外部迭代差(很多),是不是说明Stream真的不行了?先别下结论,我们再来考察一下更复杂的操作。

测试内容:给定订单列表,统计每个用户的总交易额。对比使用外部迭代手动实现和Stream API之间的性能。

我们将订单简化为<userName, price, timeStamp>构成的元组,并用Order对象来表示。测试程序ReductionTest,测试结果如下图:

分析,对于复杂的归约操作:

  1. Stream API的性能普遍好于外部手动迭代,并行Stream效果更佳;

再来考察并行度对并行效果的影响,测试结果如下:

分析,对于复杂的归约操作:

  1. 使用Stream并行归约在单核情况下性能比串行归约以及手动归约都要差,简单说就是最差的;

  2. 随着使用核数的增加,Stream并行效果逐渐变好,多核带来的效果明显。

以上两个实验说明,对于复杂的归约操作,Stream串行归约效果好于手动归约,在多核情况下,并行归约效果更佳。我们有理由相信,对于其他复杂的操作,Stream API也能表现出相似的性能表现。

五、结论

上述三个实验的结果可以总结如下:

  1. 对于简单操作,比如最简单的遍历,Stream串行API性能明显差于显示迭代,但并行的Stream API能够发挥多核特性。

  2. 对于复杂操作,Stream串行API性能可以和手动实现的效果匹敌,在并行执行时Stream API效果远超手动实现。

所以,如果出于性能考虑,

1. 对于简单操作推荐使用外部迭代手动实现

2. 对于复杂操作,推荐使用Stream API,

3. 在多核情况下,推荐使用并行Stream API来发挥多核优势

4.单核情况下不建议使用并行Stream API

如果出于代码简洁性考虑,使用Stream API能够写出更短的代码。即使是从性能方面说,尽可能的使用Stream API也另外一个优势,那就是只要Java Stream类库做了升级优化,代码不用做任何修改就能享受到升级带来的好处。

原文地址:https://www.cnblogs.com/moon0201/p/10559326.html

时间: 2024-08-30 10:58:22

Java 8中用法优雅的Stream,性能也"优雅"吗?的相关文章

java 8 中的stream

Java 8 中的 Streams 详解 Java 8 是迄今为止在语义上改动上最大的一个平台.除了最显著的 Lambda 表达式之外,还有很多初次见面的特性,例如本文主题的 Streams API.这里介绍了它出现的背景和具体用法. 为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesi

java编程中的性能提升问题

java编程中的性能提升 软件产品犹如一栋大楼,大楼在建设初期,会有楼房规划,建筑构想,打牢地基,后面才是施工人员进行进行实质性的建设.要保证软件产品的高质量,优秀的架构,优秀的产品设计,是产生高质量的前提.同时,没有过硬的编码实现,一样得不到预期的效果.纵观现在的产品,产品架构没多大差别,基本运用基线版本进行局点定制.而系统中的一些功能性能常常不过关,问题往往就出在编码实现上.这块是开发人员在开发过程中需要注意的.在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身.养成良

【Java学习笔记之二十】final关键字在Java继承中的用法小结

谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. 一.final关键字的基本用法 在Java中,final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量).下面就从这三个方面来了解一下final关键字的基本用法. 1.修饰类   当用final修饰一个类时,表明这个类不能被继承.也就是说,如果一个类你永远不会让他被继承,就可以用final

【Java学习笔记之二十二】解析接口在Java继承中的用法及实例分析

一.定义 Java接口(Interface),是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能). 接口定义的一般形式为: [访问控制符]interface <接口名> { 类型标识符final 符号常量名n = 常数: 返回值类型  方法名([参数列表]); … } 二.接口的特点 1.Java接口中的成员变量默认都是public,static,final类型的(都可省略),必须被显

Java编程中“为了性能”尽量要做到的一些地方

下面是参考网络资源总结的一些在Java编程中尽可能要做到的一些地方. 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: 第一,控制资源的使用,通过线程同步来控制资源的并发访问: 第二,控制实例的产生,以达到节约资源的目的: 第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信. 2. 尽量避免随意使用静态变量 要知道,当某个对象被定义为stataic变量所

java编程中&#39;为了性能&#39;一些尽量做到的地方

java编程中'为了性能'一些尽量做到的地方 2011-08-16 14:34:59|  分类: JAVA |  标签:java编程  缓存经常使用的对象  |举报|字号 最近的机器内存又爆满了,出了新增机器内存外,还应该好好review一下我们的代码,有很多代码编写过于随意化,这些不好的习惯或对程序语言的不了解是应该好好打压打压了. 下面是参考网络资源和总结一些在java编程中尽可能做到的一些地方- 1.尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并

在Java 线程中返回值的用法

http://icgemu.iteye.com/blog/467848 在Java 线程中返回值的用法 博客分类: Java Javathread 有时在执行线程中需要在线程中返回一个值:常规中我们会用Runnable接口和Thread类设置一个变量:在run()中改变变量的值,再用一个get方法取得该值,但是run何时完成是未知的:我们需要一定的机制来保证. 在在Java se5有个Callable接口:我们可以用该接口来完成该功能: 代码如: Java代码   package com.thr

详解Java 8中Stream类型的“懒”操作

在进入正题之前,我们需要先引入Java 8中Stream类型的两个很重要的操作: 中间和终结操作(Intermediate and Terminal Operation) Stream类型有两种类型的方法: 中间操作(Intermediate Operation) 终结操作(Terminal Operation) 官方文档给出的描述为[不想看字母的请直接跳过]: Stream operations are divided into intermediate and terminal operat

Java编程中提高性能的几点建议

尽量减少对变量的重复计算 如 for(int i=0;i<list.size();i++) 应该改为 for(int i=0,len=list.size();i<len;i++) 并且在循环中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 尽量使用移位来代替'a/b'的操作 "/"是一个代价很高的操作,使用移位的操作将会更快和更有效 如 int num = a / 4; int num = a /