Java8实战-总结29

Java8实战-总结29

  • 并行数据处理与性能
    • 并行流
      • 将顺序流转换为并行流
      • 测量流性能

并行数据处理与性能

到目前为止,Stream接口最重要的好处是可以对这些集合执行操作流水线,能够自动利用计算机上的多个内核。

例如,在Java 7之前,并行处理数据集合非常麻烦。第一,需要明确地把包含数据的数据结构分成若干子部分。第二,要给每个子部分分配一个独立的线程。第三,需要在恰当的时候对它们进行同步来避免不希望出现的竞争条件,等待所有线程完成,最后把这些部分结果合并起来。Java 7引入了一个叫作分支/合并的框架,让这些操作更稳定、更不易出错。

Stream接口允许声明性地将顺序流变为并行流。了解并行流内部是如何工作的很重要,因为如果忽视这一方面,就可能因误用而得到意外的(很可能是错的)结果。

并行流

stream接口可以让你非常方便地处理它的元素:可以通过对收集源调用parallelStream方法来把集合转换为并行流。并行流就是一个把内容分成多个数据块,并用不同的线程分别处理每个数据块的流。这样一来,就可以自动把给定操作的工作负荷分配给多核处理器的所有内核,让它们都忙起来。

假设需要写一个方法,接受数字n作为参数,并返回从1到给定参数的所有数字的和。一个直接的方法是生成一个无穷大的数字流,把它限制到给定的数目,然后用对两个数字求和的Binaryoperator来归约这个流,如下所示:

	public static long sequentialSum(long n) {return Stream.iterate(1L, i -> i + 1) //生成自然数无限流.limit(n) //限制到前n个数.reduce(0L, Long::sum); //对所有数字求和来归纳流

用更为传统的Java术语来说,这段代码与下面的迭代等价:

	public static long iterativesum(long n) {long result = 0;for(1ong i = 1L; i <= n; i++) {result += i;}return result;}

这似乎是利用并行处理的好机会,特别是n很大的时候。那怎么入手呢?要对结果变量进行同步吗?用多少个线程呢?谁负责生成数呢?谁来做加法呢?

用并行流的话,这问题就简单多了。

将顺序流转换为并行流

可以把流转换成并行流,从而让前面的函数归约过程(也就是求和)并行运行——对顺序流调用parallel方法:

	public static long parallelSum(long n) {return Stream.iterate(1L, i -> i + 1).limit(n)·paralle1() //将流转换为并行流.reduce(0L, Long::sum);

在上面的代码中的不同之处在于Stream在内部分成了几块。因此可以对不同的块独立并行进行归纳操作,如下图所示。最后,同一个归纳操作会将各个子流的部分归纳结果合并起来,得到整个原始流的归纳结果。
在这里插入图片描述
请注意,在现实中,对顺序流调用parallel方法并不意味着流本身有任何实际的变化。它在内部实际上就是设了一个boolean标志,表示你想让调用parallel之后进行的所有操作都并行执行。类似地,你只需要对并行流调用sequential方法就可以把它变成顺序流。请注意,你可能以为把这两个方法结合起来,就可以更细化地控制在遍历流时哪些操作要并行执行,哪些要顺序执行。例如,可以这样做:

stream.parallel().filter(...).sequential().map(...)·parallel().reduce();

但最后一次parallelsequential调用会影响整个流水线。在本例中,流水线会并行执行,因为最后调用的是它。

回到我们的数字求和练习,我们说过,在多核处理器上运行并行版本时,会有显著的性能提升。现在你有三个方法,用三种不同的方式(迭代式、顺序归纳和并行归纳)做完全相同的操作,让我们看看谁最快吧!

测量流性能

虽然声称并行求和方法应该比顺序和迭代方法性能好。然而在软件工程上,靠猜绝对不是什么好办法,特别是在优化性能时,你应该始终遵循三个黄金规则:测量,测量,再测量。为此,可以开发一个方法,如下所示,测量对前n个自然数求和的函数的性能:

public long measureSumPerf(Function<Long, Long> adder, long n) {long fastest = Long.MAX_VALUE;for(int i = 0; i < 10; i++) {long start = System.nanoTime();long sum = adder.apply(n);long duration =(System.nanoTime() - start) / 1_000_000;System.out.println("Result:"+ sum);if(duration < fastest) fastest = duration;}return fastest;
}

这个方法接受一个函数和一个long作为参数。它会对传给方法的long应用函数10次,记录每次执行的时间(以毫秒为单位),并返回最短的一次执行时间。假设你把先前开发的所有方法都放进了一个名为ParallelStreams的类,你就可以用这个框架来测试顺序加法器函数对前一千万个自然数求和要用多久:

System.out.println("Sequential sum done in:" + measureSumPerf(ParallelStreams::sequentialSum,10_000_000) + " msecs");

请注意,我们对这个结果应持保留态度。影响执行时间的因素有很多,比如你的电脑支持多少个内核。你可以在自己的机器上跑一下这些代码。我们运行它,输出是这样的:

Sequential sum done in: 97 msecs

用传统for循环的迭代版本执行起来应该会快很多,因为它更为底层,更重要的是不需要对原始类型做任何装箱或拆箱操作。如果你试着测量它的性能,

System.out.println("Iterative sum done in:" +
measureSumPerf(ParallelStreams::iterativesum, 10_000_000) + " msecs");

将得到:

Iterative sum done in: 2 msecs

现在来对函数的并行版本做测试:

System.out.println("Parallel sum done in:" + measureSumPerf(ParallelStreams::parallelSum, 10_000_000) + " msecs");

看看会出现什么情况:

Parallel sum done in: 164 msecs

这相当令人失望,求和方法的并行版本比顺序版本要慢很多。如何解释这个意外的结果呢?这里实际上有两个问题:

  • iterate生成的是装箱的对象,必须拆箱成数字才能求和;
  • 我们很难把iterate分成多个独立块来并行执行。

第二个问题更有意思一点,因为你必须意识到某些流操作比其他操作更容易并行化。具体来说,iterate很难分割成能够独立执行的小块,因为每次应用这个函数都要依赖前一次应用的结果,如下图所示。
在这里插入图片描述
这意味着,在这个特定情况下,归纳进程不是像上面图中那样进行的;整张数字列表在归纳过程开始时没有准备好,因而无法有效地把流划分为小块来并行处理。把流标记成并行,你其实是给顺序处理增加了开销,它还要把每次求和操作分到一个不同的线程上。

这就说明了并行编程可能很复杂,有时候甚至有点违反直觉。如果用得不对(比如采用了一个不易并行化的操作,如iterate),它甚至可能让程序的整体性能更差,所以在调用那个看似神奇的parallel操作时,了解背后到底发生了什么是很有必要的。

使用更有针对性的方法
那到底要怎么利用多核处理器,用流来高效地并行求和呢?LongStream.rangeClosed方法与iterate相比有两个优点。

  • LongStream.rangeClosed直接产生原始类型的long数字,没有装箱拆箱的开销。
  • LongStream.rangeClosed会生成数字范围,很容易拆分为独立的小块。例如,范围1~ 20可分为1~ 5、6~ 10、11~ 15和16~20。

让我们先看一下它用于顺序流时的性能如何,看看拆箱的开销到底要不要紧:

public static long rangedsum(long n) {return LongStream.rangeclosed(1, n).reduce(OL, Long::sum);
}

这一次的输出是:

Ranged sum done in: 17 msecs

这个数值流比前面那个用iterate工厂方法生成数字的顺序执行版本要快得多,因为数值流避免了非针对性流那些没必要的自动装箱和拆箱操作。由此可见,选择适当的数据结构往往比并行化算法更重要。但要是对这个新版本应用并行流呢?

public static long parallelRangedsum(long n) {return LongStream.rangeClosed(1, n)·parallel().reduce(0L, Long::sum);
}

现在把这个函数传给测试方法:

System.out.println("Parallel range sum done in:" + measureSumPerf(ParallelStreams::parallelRangedsum, 10_000_000) + " msecs");

会得到:

Parallel range sum done in: 1 msecs

终于,得到了一个比顺序执行更快的并行归纳,因为这一次归纳操作可以像上面图中那样执行了。这也表明,使用正确的数据结构然后使其并行工作能够保证最佳的性能。

尽管如此,请记住,并行化并不是没有代价的。并行化过程本身需要对流做递归划分,把每个子流的归纳操作分配到不同的线程,然后把这些操作的结果合并成一个值。但在多个内核之间移动数据的代价也可能比想的要大,所以很重要的一点是要保证在内核中并行执行工作的时间比在内核之间传输数据的时间长。总而言之,很多情况下不可能或不方便并行化。然而,在使用并行Stream加速代码之前,必须确保用得对;如果结果错了,算得快就毫无意义了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/111485.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【测试开发】概念篇 · 测试相关基础概念 · 常见开发模型 · 常见测试模型

【测试开发】概念篇 文章目录 【测试开发】概念篇1. 什么是需求1.1 需求的定义1.2 为什么有需求1.3 测试人员眼里的需求1.4 如何深入了解需求 2. 什么是测试用例2.1 为什么有测试用例2.2 练习>手机打电话 3. 什么是bug4. 开发模型和测试模型4.1 软件生命周期4.2 开发模型4.3…

六、数学建模之插值与拟合

1.概念 2.例题和matlab代码求解 一、概念 1.插值 &#xff08;1&#xff09;定义&#xff1a;插值是数学和统计学中的一种技术&#xff0c;用于估算在已知数据点之间的未知数据点的值。插值的目标是通过已知数据点之间的某种函数或方法来估计中间位置的数值。插值通常用于数…

Boostrap对HTML的表格的设计和优化

目录 01-Bootstrap的默认表格风格02-没有边线-边界的表格03-行与行的背景颜色交替变换(条纹样式)04-给表格加上边框效果05-鼠标移到行上时该行的颜色加深06-把表格的padding值缩减一半,使表格看起来更紧凑07-为表格的行或单元格设置颜色 01-Bootstrap的默认表格风格 Bootstrap…

【OpenSSL】VC编译OpenSSL

VC编译OpenSSL 编译工具准备编译OpenSSL建立Hello World工程创建VS工程 编译工具准备 安装好Visual Studio。安装Perl, 主要是用来生成nmake的。准备好汇编语言编译工具nasm,并添加到path路径。下载好Open SSL源代码。 编译OpenSSL 安装Perl,并加入到path路径&#xff0c;检验…

相机HAL

相机HAL 1、概览实现 HAL2、相机 HAL2.1 AIDL 相机 HAL2.2 相机 HAL3 功能2.3 Camera HAL1 概览 相机 HAL 相机 实现 HAL android12-release 1、概览实现 HAL HAL 位于 相机驱动程序 和 更高级别的 Android 框架 之间&#xff0c;它定义您必须实现的接口&#xff0c;以便应用…

ESP-IDF学习——1.环境安装与hello-world

ESP-IDF学习——1.环境安装与hello-world 0.前言一、环境搭建1.官方IDE工具2.vscode图形化配置 二、示例工程三、自定义工程四、点灯五、总结 0.前言 最近在学习freertos&#xff0c;但由于买的书还没到&#xff0c;所以先捣鼓捣鼓ESP-IDF&#xff0c;因为这个比Arduino更接近底…

Bash脚本学习:AWK, SED

1. AWK AWK 是一种编程语言&#xff0c;设计用于处理文件或数据流中基于文本的数据&#xff0c;或者使用 shell 管道。 可以将 awk 与 shell 脚本结合使用或直接在 shell 提示符下使用。 以上展示使用AWK分别打印第一个位置变量和第二个位置变量。 建立一个文档 csvtest.cs…

什么是无人机全自动飞行系统?概念、构成、作用深度解析

无人机的工业化应用深入催生出新的痛点&#xff0c;无人机应用飞手培养难、成本高、技术参差不齐&#xff0c;以及应急响应和采集作业价值等没有得到充分释放&#xff0c;由此无人机自动飞行系统、无人机自动机场横空出世&#xff0c;因其无人化、自动化、无人机值守的应用特性…

设计模式之十:状态模式

状态模式通过改变对象内部的状态来帮助对象控制自己的行为。 这是一张状态图&#xff0c;其中每个圆圈都是一个状态。 最简单&#xff0c;第一反应的实现就是使用一个变量来控制状态值&#xff0c;并在方法内书写条件代码来处理不同情况。 package headfirst.designpatterns.…

Springboot 实践(18)Nacos配置中心参数自动刷新测试

前文讲解了Nacos 2.2.3配置中心的服务端的下载安装&#xff0c;和springboot整合nacos的客户端。Springboot整合nacos关键在于使用的jar版本要匹配&#xff0c;文中使用版本如下&#xff1a; ☆ springboot版本: 2.1.5.RELEASE ☆ spring cloud版本 Greenwich.RELEASE ☆ sp…

72、Spring Data JPA 的 Specification 动态查询

Specification&#xff1a;规范、规格 ★ Specification查询 它也是Spring Data提供的查询——是对JPA本身 Criteria 动态查询 的包装。▲ 为何要有动态查询 页面上常常会让用户添加不同的查询条件&#xff0c;程序就需要根据用户输入的条件&#xff0c;动态地组合不同的查询…

ConfigMaps-2

文章目录 主要内容一.Volume 挂载 ConfigMap1.创建一个Pod&#xff0c;起挂载的内容&#xff0c;将来自下面的configmap&#xff1a;代码如下&#xff08;示例&#xff09;: 2.解释 二.环境变量 ConfigMap1.创建一个名为 mysqlpass 且包含 passwordABCabc123 的 configmap&…