Java8实战-总结30

2023-09-19 00:02:13

并行数据处理与性能

并行流

正确使用并行流

错用并行流而产生错误的首要原因,就是使用的算法改变了某些共享状态。下面是另一种实现对前n个自然数求和的方法,但这会改变一个共享累加器:

public static long sideEffectSum(long n) {
	Accumulator accumulator = new Accumulator();
	LongStream.rangeClosed(1, n).forEach(accumulator::add);
	return accumulator.total;
}

public class Accumulator {
	public long total = 0;
	public void add(long value) { total += value; }
}

这种代码非常普遍,特别是对那些熟悉指令式编程范式的程序员来说。这段代码和指令式迭代数字列表的方式很像:初始化一个累加器,一个个遍历列表中的元素,把它们和累加器相加。

那这种代码它在本质上就是顺序的。每次访问total都会出现数据竞争。如果你尝试用同步来修复,那就完全失去并行的意义了。为了说明这一点,让我们试着把Stream变成并行的:

public static long sideEffectParallelSum(long n) {
	Accumulator accumulator = new Accumulator();
	LongStream.rangeClosed(1, n).parallel().forEach(accumulator::add);
	return accumulator.total;
}

用测试框架来执行这个方法,并打印每次执行的结果:

System.out.println("SideEffect parallel sum done in:" + measurePerf(ParallelStreams::sideEffectParallelSum, 10_000_000L) + " msecs");

你可能会得到类似于下面这种输出:

Result: 5959989000692
Result:7425264100768
Result: 6827235020033
Result:7192970417739
Result: 6714157975331
Result:7497810541907
Result: 6435348440385
Result:6999349840672
Result:7435914379978
Result:7715125932481
SideEffect parallel sum done in: 49 msecs

这回方法的性能无关紧要了,唯一要紧的是每次执行都会返回不同的结果,都离正确值50000005000000差很远。这是由于多个线程在同时访问累加器,执行total += value,而这一句虽然看似简单,却不是一个原子操作。问题的根源在于,forEach中调用的方法有副作用,它会改变多个线程共享的对象的可变状态。要是你想用并行Stream又不想引发类似的意外,就必须避免这种情况。
共享可变状态会影响并行流以及并行计算。记住要避免共享可变状态,确保并行Stream得到正确的结果。接下来,会提供一些实用建议,你可以由此判断什么时候可以利用并行流来提升性能。

高效使用并行流

一般而言,想给出任何关于什么时候该用并行流的定量建议都是不可能也毫无意义的,因为任何类似于“仅当至少有一千个(或一百万个或随便什么数字)元素的时候才用并行流)”的建议对于某台特定机器上的某个特定操作可能是对的,但在略有差异的另一种情况下可能就是大错特错。尽管如此,至少可以提出一些定性意见,帮你决定某个特定情况下是否有必要使用并行流。

  • 如果有疑问,测量。把顺序流转成并行流轻而易举,但却不一定是好事。我们已经指出,并行流并不总是比顺序流快。此外,并行流有时候会和你的直觉不一致,所以在考虑选择顺序流还是并行流时,第一个也是最重要的建议就是用适当的基准来检查其性能。
  • 留意装箱。自动装箱和拆箱操作会大大降低性能。Java 8中有原始类型流(IntStreamLongStreamDoubleStream)来避免这种操作,但凡有可能都应该用这些流。
  • 有些操作本身在并行流上的性能就比顺序流差。特别是limitfindFirst等依赖于元素顺序的操作,它们在并行流上执行的代价非常大。例如,findAny会比findFirst性能好,因为它不一定要按顺序来执行。你总是可以调用unordered方法来把有序流变成无序流。那么,如果你需要流中的n个元素而不是专门要前n个的话,对无序并行流调用limit可能会比单个有序流(比如数据源是一个List)更高效。
  • 还要考虑流的操作流水线的总计算成本。设N是要处理的元素的总数,Q是一个元素通过流水线的大致处理成本,则N*Q就是这个对成本的一个粗略的定性估计。Q值较高就意味着使用并行流时性能好的可能性比较大。
  • 对于较小的数据量,选择并行流几乎从来都不是一个好的决定。并行处理少数几个元素的好处还抵不上并行化造成的额外开销。
  • 要考虑流背后的数据结构是否易于分解。例如,ArrayList的拆分效率比LinkedList高得多,因为前者用不着遍历就可以平均拆分,而后者则必须遍历。另外,用range工厂方法创建的原始类型流也可以快速分解。
  • 流自身的特点,以及流水线中的中间操作修改流的方式,都可能会改变分解过程的性能。例如,一个SIZED流可以分成大小相等的两部分,这样每个部分都可以比较高效地并行处理,但筛选操作可能丢弃的元素个数却无法预测,导致流本身的大小未知。
  • 还要考虑终端操作中合并步骤的代价是大是小(例如Collector中的combiner方法)。如果这一步代价很大,那么组合每个子流产生的部分结果所付出的代价就可能会超出通过并行流得到的性能提升。

下表按照可分解性总结了一些流数据源适不适于并行。 流的数据源和可分解性:在这里插入图片描述

小结

  • 内部迭代让你可以并行处理一个流,而无需在代码中显式使用和协调不同的线程。
  • 虽然并行处理一个流很容易,却不能保证程序在所有情况下都运行得更快。并行软件的行为和性能有时是违反直觉的,因此一定要测量,确保你并没有把程序拖得更慢。
  • 像并行流那样对一个数据集并行执行操作可以提升性能,特别是要处理的元素数量庞大,或处理单个元素特别耗时的时候。
  • 从性能角度来看,使用正确的数据结构,如尽可能利用原始流而不是一般化的流,几乎总是比尝试并行化某些操作更为重要。
更多推荐

SpringMVC之自定义注解

目录一.什么是Java注解1.简介2.注解的分类3.JDK元注解二.自定义注解1.自定义注解的分类1.1.标记Annotation:1.2.元数据Annotation:2.如何使用自定义注解3.案例演示3.1获取类、方法及属性上的注解值3.2@Inherited的使用3.3获取类属性上的注解属性值3.4获取参数修饰注解

【Django入门】第一个Django项目

Django,广为人知的PythonWeb框架,以其强大而又灵活的特点脱颖而出。其宣传口号是:“为完美主义者开发的框架”。这篇文章将为你揭示创建第一个Django项目的魔法以及Django项目的基本结构。为什么选择Django?在深入学习前,我们先看看为什么要选择Django:快速开发:Django遵循“约定优于配置”

《ADS2011射频电路设计与仿真实例》功率放大器设计的输入输出匹配

徐兴福这本书的6.6Smith圆图匹配这一节中具体匹配时,直接给出了电容与串联微带的值,没有给出推导过程,我一开始以为是省略了详细推导过程,后来发现好像基本上是可以随便自己设的。以输入匹配(书本6.6.4输入匹配电路的建立)为例:因为它这里要求要在Q=1.5的等Q圆里面进行匹配,可以按照书本的操作显示出1.5的等Q圆,

【MySQL入门实战5】-Linux PRM 包安装MySQL

📢📢📢📣📣📣哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA工作经验一位上进心十足的【大数据领域博主】!😜😜😜中国DBA联盟(ACDU)成员,目前从事DBA及程序编程擅长主流数据Oracle、MySQL、PG运维开发,备份恢复,安装迁移,性能优化、故障应急处理等。✨如果有对【

列表页面新增 字段查询 ,点击查询后,前端页面和后端控制台 出现红色报错信息,查询数据失败。

项目场景:项目场景简述:列表页面新增字段查询,点击查询后,前端页面和后端控制台出现红色报错信息,查询数据失败。问题描述问题描述:<el-selectv-model="dataForm.engineerId"clearable@focus="getEngineerList"placeholder="请选择"filtera

真实的产品开发中,后端的设计规约可以写哪些

真实的产品开发中,后端的设计规约可以写哪些产品开发的后端设计规约通常包括以下内容:数据模型设计:详细描述数据库的结构,包括数据表的设计、字段的定义和关系的设置等。业务逻辑设计:详细描述后端的业务逻辑,包括各种算法的设计、业务流程的控制等。API设计:定义后端提供的各种服务接口,包括接口的URL、请求方法、请求参数、响应

CMMI认证是什么?为什么IT类企业都在申请?

如今,越来越多的公司都会去申请获得CMMI的认证,也会以通过认证为荣,那么CMMI认证的意义和目的是什么?怎样可以拿到CMMI认证呢?什么是CMMI?CMMI是由美国卡内基梅隆大学的软件工程研究所(SEI)提出的能力成熟度模型集成,致力于以更加系统和一致的框架来指导组织改善软件过程,提高产品和服务的开发、获取和维护能力

华为云ROMA Connect亮相Gartner®全球应用创新及商业解决方案峰会,助力企业应用集成和数字化转型

9月13日-9月14日Gartner全球应用创新及商业解决方案峰会在伦敦举行本届峰会以“重塑软件交付,驱动业务价值”为主题,全球1000多位业内专家交流最新的企业应用、软件工程、解决方案架构、集成与自动化、API等企业IT战略和新兴技术热门话题。9月13日,华为流程ITROMA首席专家陆昕,华为云ROMAConnect

在JavaScript中,什么是柯里化(currying)?

聚沙成塔·每天进步一点点⭐专栏简介⭐柯里化(Currying)⭐写在最后⭐专栏简介前端入门之旅:探索Web开发的奇妙世界欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发者,这里都将为你提供一个系统而又亲切的学习

html播放视频

文章目录<embed>标签<object>标签<video>标签<video>浏览器支持视频格式与浏览器的支持DOM元素提供的方法、属性和事件自定义控制栏<embed>标签<embed>标签的作用是在HTML页面中嵌入多媒体元素。前提:浏览器支持Flash。iPad和iPhone不能显示Flash视频。视频不能转成其他

【ES6知识】 Reflect 与 Proxy

文章目录前言一、Proxy代理对象1.1基本应用1.2同一个拦截器函数,可以设置拦截多个操作:1.3Proxy支持的拦截操作一览,一共13种:二、Reflect对象2.1基本使用2.2`Reflect`对象一共有13个静态方法三、使用Proxy实现观察者模式前言Proxy与Reflect是ES6为了操作对象引入的API

热文推荐