Java 8 Stream的性能到底如何?

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

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

OSCentOS 6.7 x86_64CPUIntel Xeon X5675, 12M Cache 3.06 GHz, 6 Cores 12 Threads内存96GBJDKjava version 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM

测试方法和测试数据

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

-XX:+UseConcMarkSweepGC -Xms10G -Xmx10G
-XX:CompileThreshold=10000
Stream并行执行时用到 ForkJoinPool.commonPool() 得到的线程池,为控制并行度我们使用Linux的 taskset 命令指定JVM可用的核数。

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

实验一 基本类型迭代

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

测试程序代码:

/**

  • java -server -Xms10G -Xmx10G -XX:+PrintGCDetails
  • -XX:+UseConcMarkSweepGC -XX:CompileThreshold=1000 lee/IntTest
  • taskset -c 0-[0,1,3,7] java ...
  • @author CarpenterLee
    */
    public class IntTest {
    public static void main(String[] args) {
    new IntTest().doTest();
    }
    public void doTest(){
    warmUp();
    int[] lengths = {
    10000,
    100000,
    1000000,
    10000000,
    100000000,
    1000000000
    };
    for(int length : lengths){
    System.out.println(String.format("---array length: %d---", length));
    int[] arr = new int[length];
    randomInt(arr);
    int times = 4;
    int min1 = 1;
    int min2 = 2;
    int min3 = 3;
    long startTime;
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    min1 = minIntFor(arr);
    }
    TimeUtil.outTimeUs(startTime, "minIntFor time:", times);
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    min2 = minIntStream(arr);
    }
    TimeUtil.outTimeUs(startTime, "minIntStream time:", times);
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    min3 = minIntParallelStream(arr);
    }
    TimeUtil.outTimeUs(startTime, "minIntParallelStream time:", times);
    System.out.println(min1==min2 && min2==min3);
    }
    }
    private void warmUp(){
    int[] arr = new int[100];
    randomInt(arr);
    for(int i=0; i<20000; i++){
    // minIntFor(arr);
    minIntStream(arr);
    minIntParallelStream(arr);
    }
    }
    private int minIntFor(int[] arr){
    int min = Integer.MAX_VALUE;
    for(int i=0; i<arr.length; i++){
    if(arr[i]<min)
    min = arr[i];
    }
    return min;
    }
    private int minIntStream(int[] arr){
    return Arrays.stream(arr).min().getAsInt();
    }
    private int minIntParallelStream(int[] arr){
    return Arrays.stream(arr).parallel().min().getAsInt();
    }
    private void randomInt(int[] arr){
    Random r = new Random();
    for(int i=0; i<arr.length; i++){
    arr[i] = r.nextInt();
    }
    }
    }
    测试结果如下图:

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

对于基本类型Stream串行迭代的性能开销明显高于外部迭×××销(两倍);
Stream并行迭代的性能比串行迭代和外部迭代都好。
并行迭代性能跟可利用的核数有关,上图中的并行迭代使用了全部12个核,为考察使用核数对性能的影响,我们专门测试了不同核数下的Stream并行迭代效果:

分析,对于基本类型:

使用Stream并行API在单核情况下性能很差,比Stream串行API的性能还差;
随着使用核数的增加,Stream并行效果逐渐变好,比使用for循环外部迭代的性能还好。
以上两个测试说明,对于基本类型的简单迭代,Stream串行迭代性能更差,但多核情况下Stream迭代时性能较好。

实验二 对象迭代

再来看对象的迭代效果。

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

测试程序代码:

/**

  • java -server -Xms10G -Xmx10G -XX:+PrintGCDetails
  • -XX:+UseConcMarkSweepGC -XX:CompileThreshold=1000 lee/StringTest
  • taskset -c 0-[0,1,3,7] java ...
  • @author CarpenterLee
    */
    public class StringTest {
    public static void main(String[] args) {
    new StringTest().doTest();
    }
    public void doTest(){
    warmUp();
    int[] lengths = {
    10000,
    100000,
    1000000,
    10000000,
    20000000,
    40000000
    };
    for(int length : lengths){
    System.out.println(String.format("---List length: %d---", length));
    ArrayList<String> list = randomStringList(length);
    int times = 4;
    String min1 = "1";
    String min2 = "2";
    String min3 = "3";
    long startTime;
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    min1 = minStringForLoop(list);
    }
    TimeUtil.outTimeUs(startTime, "minStringForLoop time:", times);
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    min2 = minStringStream(list);
    }
    TimeUtil.outTimeUs(startTime, "minStringStream time:", times);
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    min3 = minStringParallelStream(list);
    }
    TimeUtil.outTimeUs(startTime, "minStringParallelStream time:", times);
    System.out.println(min1.equals(min2) && min2.equals(min3));
    // System.out.println(min1);
    }
    }
    private void warmUp(){
    ArrayList<String> list = randomStringList(10);
    for(int i=0; i<20000; i++){
    minStringForLoop(list);
    minStringStream(list);
    minStringParallelStream(list);
    }
    }
    private String minStringForLoop(ArrayList<String> list){
    String minStr = null;
    boolean first = true;
    for(String str : list){
    if(first){
    first = false;
    minStr = str;
    }
    if(minStr.compareTo(str)>0){
    minStr = str;
    }
    }
    return minStr;
    }
    private String minStringStream(ArrayList<String> list){
    return list.stream().min(String::compareTo).get();
    }
    private String minStringParallelStream(ArrayList<String> list){
    return list.stream().parallel().min(String::compareTo).get();
    }
    private ArrayList<String> randomStringList(int listLength){
    ArrayList<String> list = new ArrayList<>(listLength);
    Random rand = new Random();
    int strLength = 10;
    StringBuilder buf = new StringBuilder(strLength);
    for(int i=0; i<listLength; i++){
    buf.delete(0, buf.length());
    for(int j=0; j<strLength; j++){
    buf.append((char)(‘a‘+rand.nextInt(26)));
    }
    list.add(buf.toString());
    }
    return list;
    }
    }
    测试结果如下图:

结果分析如下:

对于对象类型Stream串行迭代的性能开销仍然高于外部迭×××销(1.5倍),但差距没有基本类型那么大。
Stream并行迭代的性能比串行迭代和外部迭代都好。
再来单独考察Stream并行迭代效果:

分析,对于对象类型:

使用Stream并行API在单核情况下性能比for循环外部迭代差;
随着使用核数的增加,Stream并行效果逐渐变好,多核带来的效果明显。
以上两个测试说明,对于对象类型的简单迭代,Stream串行迭代性能更差,但多核情况下Stream迭代时性能较好。

实验三 复杂对象归约

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

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

我们将订单简化为 <userName, price, timeStamp> 构成的元组,并用 Order 对象来表示。

测试程序代码:

/**

  • java -server -Xms10G -Xmx10G -XX:+PrintGCDetails
  • -XX:+UseConcMarkSweepGC -XX:CompileThreshold=1000 lee/ReductionTest
  • taskset -c 0-[0,1,3,7] java ...
  • @author CarpenterLee
    */
    public class ReductionTest {
    public static void main(String[] args) {
    new ReductionTest().doTest();
    }
    public void doTest(){
    warmUp();
    int[] lengths = {
    10000,
    100000,
    1000000,
    10000000,
    20000000,
    40000000
    };
    for(int length : lengths){
    System.out.println(String.format("---orders length: %d---", length));
    List<Order> orders = Order.genOrders(length);
    int times = 4;
    Map<String, Double> map1 = null;
    Map<String, Double> map2 = null;
    Map<String, Double> map3 = null;
    long startTime;
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    map1 = sumOrderForLoop(orders);
    }
    TimeUtil.outTimeUs(startTime, "sumOrderForLoop time:", times);
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    map2 = sumOrderStream(orders);
    }
    TimeUtil.outTimeUs(startTime, "sumOrderStream time:", times);
    startTime = System.nanoTime();
    for(int i=0; i<times; i++){
    map3 = sumOrderParallelStream(orders);
    }
    TimeUtil.outTimeUs(startTime, "sumOrderParallelStream time:", times);
    System.out.println("users=" + map3.size());
    }
    }
    private void warmUp(){
    List<Order> orders = Order.genOrders(10);
    for(int i=0; i<20000; i++){
    sumOrderForLoop(orders);
    sumOrderStream(orders);
    sumOrderParallelStream(orders);
    }
    }
    private Map<String, Double> sumOrderForLoop(List<Order> orders){
    Map<String, Double> map = new HashMap<>();
    for(Order od : orders){
    String userName = od.getUserName();
    Double v;
    if((v=map.get(userName)) != null){
    map.put(userName, v+od.getPrice());
    }else{
    map.put(userName, od.getPrice());
    }
    }
    return map;
    }
    private Map<String, Double> sumOrderStream(List<Order> orders){
    return orders.stream().collect(
    Collectors.groupingBy(Order::getUserName,
    Collectors.summingDouble(Order::getPrice)));
    }
    private Map<String, Double> sumOrderParallelStream(List<Order> orders){
    return orders.parallelStream().collect(
    Collectors.groupingBy(Order::getUserName,
    Collectors.summingDouble(Order::getPrice)));
    }
    }
    class Order{
    private String userName;
    private double price;
    private long timestamp;
    public Order(String userName, double price, long timestamp) {
    this.userName = userName;
    this.price = price;
    this.timestamp = timestamp;
    }
    public String getUserName() {
    return userName;
    }
    public double getPrice() {
    return price;
    }
    public long getTimestamp() {
    return timestamp;
    }
    public static List<Order> genOrders(int listLength){
    ArrayList<Order> list = new ArrayList<>(listLength);
    Random rand = new Random();
    int users = listLength/200;// 200 orders per user
    users = users==0 ? listLength : users;
    ArrayList<String> userNames = new ArrayList<>(users);
    for(int i=0; i<users; i++){
    userNames.add(UUID.randomUUID().toString());
    }
    for(int i=0; i<listLength; i++){
    double price = rand.nextInt(1000);
    String userName = userNames.get(rand.nextInt(users));
    list.add(new Order(userName, price, System.nanoTime()));
    }
    return list;Java 8 Stream的性能到底如何?

    原文地址:https://blog.51cto.com/14226273/2364769

    时间: 2024-10-20 13:21:05

Java 8 Stream的性能到底如何?的相关文章

Java 8中用法优雅的Stream,性能也&quot;优雅&quot;吗?

之前的文章中我们介绍了Java 8中Stream相关的API,我们提到Stream API可以极大提高Java程序员的生产力,让程序员写出高效率.干净.简洁的代码. 那么,Stream API的性能到底如何呢,代码整洁的背后是否意味着性能的损耗呢?本文我们对Stream API的性能一探究竟. 为保证测试结果真实可信,我们将JVM运行在-server模式下,测试数据在GB量级,测试机器采用常见的商用服务器,配置如下: 一.测试方法与数据 性能测试并不是容易的事,Java性能测试更费劲,因为虚拟机

java编程中的性能提升问题

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

java 编程时候的性能调优

一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util.vector; class cel { void method (vector vector) { for (int i = 0; i < vector.size (); i++) // violation ; // ... } } 更正: class cel_fixed { void metho

jdk1.8 java.util.stream.Stream类 详解

为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream.Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利.高效的聚合操作(aggregate operation),或者大批量数据操作 (

java8 函数式编程入门官方文档中文版 java.util.stream 中文版 流处理的相关概念

前言 本文为java.util.stream 包文档的译文 极其个别部分可能为了更好理解,陈述略有改动,与原文几乎一致 原文可参考在线API文档 https://docs.oracle.com/javase/8/docs/api/ Package java.util.stream Description 一些用于支持流上函数式操作的类 ,例如在集合上的map-reduce转换.例如 int sum = widgets.stream() .filter(b -> b.getColor() == R

java.util.stream 库简介

Java Stream简介 Java SE 8 中主要的新语言特性是拉姆达表达式.可以将拉姆达表达式想作一种匿名方法:像方法一样,拉姆达表达式具有带类型的参数.主体和返回类型.但真正的亮点不是拉姆达表达式本身,而是它们所实现的功能.拉姆达表达式使得将行为表达为数据变得很容易,从而使开发具有更强表达能力.更强大的库成为可能. Java SE 8 中引入的一个这样的库是 java.util.stream 包 (Streams),它有助于为各种数据来源上的可能的并行批量操作建立简明的.声明性的表达式.

Java 之 Stream 流

Stream流 在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端 一.传统遍历 1.传统集合的多步遍历代码 几乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或间接的遍历操作.而当我们需要对集合中的元素进行操作的时候,除了必需的添加.删除.获取外,最典型的就是集合遍历.例如: 1 import java.util.ArrayList; 2 import java.util.List; 3 4 pub

Java学习记录(补充八:Date类;Java流(Stream),文件(File)和IO)

Date类,Calendar类package Box1; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Random; //Date类 public class DateTest { public static void main(String[] args) { Date

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

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