ParallelStream 处理数据

Stream 接口提供了parallelStream方法来将集合转换为并行流。即将一个集合分为多个数据块,并用不同的线程分别处理每个数据块的流。

并且使用parallelStream 时无需担心内部变量控制,线程数量等问题。

如使用并行流计算1至100000累加之和:

  • 最后一次parallel或sequential调用会影响整个流水线,即如下例子中会并行执行。
  • parallelStream使用得默认核心数为Runtime.getRuntime().availableProcessors() - 1。

    可通过配置java.util.concurrent.ForkJoinPool.common.parallelism改变默认的核心数。
        Stream.iterate(1L, param1 -> Math.addExact(param1, 1))
.limit(100000)
.parallel()
.sequential()
.parallel()
.reduce(0L, Math::addExact)
.longValue();

parallelStream 性能分析

通常我们认为在数据量到达一定程度时,使用多线程计算会获得更好的性能。但实际效果应该在测量比较之后才直到。

使用并行流和顺序流别计算1至100000 的累加之和,在我的四核英特尔机器上运行结果如下:

        long start = System.currentTimeMillis();
Stream.iterate(1L, param1 -> Math.addExact(param1, 1))
.limit(100000)
.parallel()
.reduce(0L, Math::addExact)
.longValue();
System.out.println(String.format("Parallel accumulate sum, used %d ms.", System.currentTimeMillis() - start)); start = System.currentTimeMillis();
LongStream.rangeClosed(1, 100000)
.reduce(0L, Math::addExact);
System.out.println(String.format("Sequential accumulate sum, used %d ms.", System.currentTimeMillis() - start)); Parallel accumulate sum, used 64 ms.
Sequential accumulate sum, used 8 ms.

通过以上结果可以看到,并行流计算的耗时竟然是顺序流的好几倍,这与我们的预期结果差距十分的大。

要想明白这差距的原因,首先得明白影响上面并行流的速度的因素有那些:

  • 元素是否容易拆分为多个数据块, 很明显Iterate 很难拆分为多个独立数据块,因为每次应用这个函数都要依赖于前一个元素。
  • 元素是否频繁拆装箱, 流中Long -> long 频繁拆装箱也影响了效率。而LongStream 中并没有这个消耗。

修复上面两个影响并行流的速度的问题后,重新运行结果如下:


long start = System.currentTimeMillis();
LongStream.rangeClosed(1, 100000)
.parallel()
.reduce(0L, Math::addExact);
System.out.println(String.format("Parallel accumulate sum, used %d ms.", System.currentTimeMillis() - start)); start = System.currentTimeMillis();
LongStream.rangeClosed(1, 100000)
.reduce(0L, Math::addExact);
System.out.println(String.format("Sequential accumulate sum, used %d ms.", System.currentTimeMillis() - start)); Parallel accumulate sum, used 7 ms.
Sequential accumulate sum, used 3 ms.

并行流的速度得到了很大提升,这表明并行化时需要使用正确的数据结构

但是顺序流的速度却仍然更快,这说明并行化也是有代价的,如下:

  • 内核之间交换数据的花销较大。
  • 要保证在内核中的处理时间大于内核间的数据交换时间,即数据到达一定的量级。

而并行过程需要对流要递归划分,再把每个子流的归纳操作分配到不同的线程,最后把这些操作的结果合并成一个值。

在子流归纳操作时间过短时,并行化并没有带来性能提升,反而是更加慢了。

再将数据提升至上亿级别进行运算,并行流终于取得了一些领先。

        long start = System.currentTimeMillis();
LongStream.rangeClosed(1, 100000000)
.parallel()
.reduce(0L, Math::addExact);
System.out.println(String.format("Parallel accumulate sum, used %d ms.", System.currentTimeMillis() - start)); start = System.currentTimeMillis();
LongStream.rangeClosed(1, 100000000)
.reduce(0L, Math::addExact);
System.out.println(String.format("Sequential accumulate sum, used %d ms.", System.currentTimeMillis() - start));
Parallel accumulate sum, used 79 ms.
Sequential accumulate sum, used 264 ms.

高效使用ParallelStream

关于在什么地方使用parallelStream 没有绝对的建议,而是只能做定性分析。下列是一些可能影响性能的地方:

  • 测量比较,并行流并不都比顺序流快。
  • 避免拆装箱,这对性能有较大影响。可使用原始类型IntStream, LongStream等。
  • 依赖元素顺序的操作,并行性能较差。如findAny()性能会优于findFirst(),因为它不依赖于顺序。
  • 数据量大小,估算一个元素通过流水线的大概处理时间,得到处理完整个集合的处理时间。
  • 流是否易于拆分,如ArrayList 比LinkedList 更易于拆分,前者无需遍历,后者需要遍历之后才能拆分。
  • 终端操作时,合并操作的代价大小(例如Collector中的combiner方法)。

Fork/Join

ParallelStream流背后使用的基础架构是Java 7中引入的Fork/Join分支合并框架。

分支/合并框架的目的是以递归方式将可以并行的任务拆分成更小的任务,然后将每个子任务的结果合并起来生成整体结果。

这其实就是分治算法的并行版本。

Java ParallelStream的更多相关文章

  1. 深入浅出parallelStream

    援引:http://blog.csdn.net/u011001723/article/details/52794455 感谢作者的分享!感谢作者为JDK8的学习所做的努力. about Stream ...

  2. Spark案例分析

    一.需求:计算网页访问量前三名 import org.apache.spark.rdd.RDD import org.apache.spark.{SparkConf, SparkContext} /* ...

  3. 【Java】关于Java8 parallelStream并发安全的思考

    背景 Java8的stream接口极大地减少了for循环写法的复杂性,stream提供了map/reduce/collect等一系列聚合接口,还支持并发操作:parallelStream. 在爬虫开发 ...

  4. java并行之parallelStream与CompletableFuture比较

    1. import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; im ...

  5. Java 8里 Stream和parallelStream的区别

    Java中Stream和parallelStream,前者是单管,后者是多管,运行时间上做一个小对比,直接上代码: /** * * @author zhangy6 * <p>对比Strea ...

  6. Java Arrays.asList(0,1,2,3,4,5,6,7,8,9).parallelStream().forEach 进行循环获取HttpServletRequest的为Null的解决方案

    Arrays.asList(0,1,2,3,4,5,6,7,8,9).parallelStream().forEach() parallelStream是并行执行流的每个元素,也就是多线程执行,这样就 ...

  7. JAVA使用并行流(ParallelStream)时要注意的一些问题

    https://blog.csdn.net/xuxiaoyinliu/article/details/73040808

  8. Java 8函数编程轻松入门(五)并行化(parallel)

    1.并发与并行的区别 并发: 一个时间段内有几个程序都处于已启动到运行完毕之间,且这几个程序都是在同一个处理机上运行.但在任一个时刻点只有一个程序在处理机上运行 并行: 在同一个时刻,多核处理多个任务 ...

  9. Java 8 指南

    Benjamin Winterberg “Java is still not dead—and people are starting to figure that out.” 欢迎阅读我对 Java ...

随机推荐

  1. 怎样用好PS中的钢笔工具(附练习钢笔工具网站)

    想要在PS中得心应手的的描绘出自己想要的线条(也就是路径),就需要对[钢笔工具]有一个充分的理解. [钢笔工具]绘出来的线条全部都是贝赛尔曲线,所以你在学习[钢笔工具]之前,要补习一下贝赛尔曲线的常识 ...

  2. 七、JavaSE语言基础之方法

    关于方法的几个简单概念 关于方法的学习,先来明确几个简单的概念: 方法的作用:处理数据(把原始数据通过指定的算法处理后得到结果数据) 方法:在类中定义的具有特定功能的代码块 方法的意义(作用):提高代 ...

  3. 2、centos6和centos7的区别

    services使用了systemd来代替sysvinit管理. systemd是Linux下的一种init软件,由Lennart Poettering带头开发,并在LGPL 2.1及其后续版本许可证 ...

  4. 自定义组件开发:使用v-model封装el-pagination组件

    1.前言 通过封装el-pagination组件开发自定义分页组件的类似文章网上已经有很多了,但看了一圈,总是不如意,于是决定还是自己动手搞一个. 2.背景 2.1.常规分页处理方法 利用el-pag ...

  5. hdu 2159 二维完全背包

    Problem Description 最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务.久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级.现 ...

  6. Linux文件编辑工具——VIM

    Linux文件编辑工具--VIM 1.VIM基本概述 1.1 什么是vim vi 和 vim 是 Linux 下的一个文本编辑工具.(可以理解为 windows 的记事本,或 Notepad++ 1. ...

  7. 打开设置windows10内置linux功能-启用linux子系统

    第一步设置开发者模式 步骤:windows+s打开娜娜,输入设置,并点击. 点击更新与安全 点击开发者选项,选择开发者模型,弹出的对话框选确定之后等待安装完毕. 第二步:安装linux 点击确定后等待 ...

  8. 一款不错的 Go Server/API boilerplate,使用 K8S+DDD+CQRS+ES+gRPC 最佳实践构建

    Golang API Starter Kit 该项目的主要目的是使用最佳实践.DDD.CQRS.ES.gRPC 提供样板项目设置. 为开发和生产环境提供 kubernetes 配置.允许与反映生产的 ...

  9. [源码解析] 深度学习分布式训练框架 horovod (18) --- kubeflow tf-operator

    [源码解析] 深度学习分布式训练框架 horovod (18) --- kubeflow tf-operator 目录 [源码解析] 深度学习分布式训练框架 horovod (18) --- kube ...

  10. robotframework - database操作(增删改查)

    1.前置配置条件准备 a.mysql环境配置ok b.robot环境配置ok c.pip 安装robotframework-databaselibrary 2.mysql数据操作 -->以操作 ...