流性能

当我阅读Angelika Langer的Java性能教程时-Java 8流有多快? 我简直不敢相信,对于一个特定的操作,它们花费的时间比循环要长15倍。 流媒体性能真的会那么糟糕吗? 我必须找出答案!

巧合的是,我最近观看了一个有关微基准测试Java代码的精彩讨论 ,因此决定将在这里学到的东西投入工作。 因此,让我们看一下流是否真的那么慢。

总览

和往常一样,我将以沉闷的序幕开始。 这篇文章将解释为什么您应该对我在这里介绍的内容,我如何产生这些数字以及如何轻松地重复和调整基准非常谨慎。 如果您不关心这些,请直接跳至Stream Performance 。

但首先,有两个快速提示:所有基准测试代码都在GitHub上发布,并且此Google电子表格包含结果数据。

序幕

免责声明

这篇文章包含许多数字,并且数字是欺骗性的。 它们似乎都是科学的,精确的东西,它们诱使我们专注于它们的相互关系和解释。 但是,我们应该始终同样关注它们的发展!

我将在下面显示的数字是在系统上使用非常特定的测试用例生成的。 过度概括它们很容易! 我还应该补充一点,对于非平凡的基准测试技术(即那些不基于循环和手动System.currentTimeMillis() ),我只有两天的经验。

将您在此处获得的见解纳入心理表现模型时要格外小心。 隐藏在细节中的魔鬼是JVM本身,它是一个骗人的野兽。 我的基准测试很可能成为扭曲数字的优化的牺牲品。

系统

  • CPU:英特尔(R)核心(TM)i7-4800MQ CPU @ 2.70GHz
  • 内存 :Samsung DDR3 16GB @ 1.60GHz(测试完全在RAM中运行)
  • 操作系统 :Ubuntu 15.04。 内核版本3.19.0-26-通用
  • 的Java :1.8.0_60
  • 捷运 :1.10.5

基准测试

捷运

基准测试是使用JVM性能团队本身开发和使用的Java Microbenchmarking Harness(JMH)创建的。 它有完整的文档记录,易于设置和使用,并且通过示例进行的解释非常棒!

如果您喜欢随意介绍,您可能会喜欢2013年Devoxx UK的Aleksey Shipilev的演讲 。

设定

为了产生可靠的结果,基准测试要单独和重复运行。 每个基准测试方法都有一个单独的运行,该运行由几个分支组成 ,每个分支在实际测量迭代之前运行许多预热迭代。

我分别使用50,000、500,000、5,000'000、10'000'000和50'000'000元素运行基准测试。 除了最后一个以外,所有的分支都有两个,分别由五个预热和五个测量迭代组成,其中每个迭代持续三秒钟。 最后一个的一部分运行在一个分支中,进行了两次热身和三个测量迭代,每个迭代持续30秒。

Langer的文章指出,它们的数组填充有随机整数。 我将此与更令人愉快的情况进行了比较,在这种情况下,数组中的每个int等于其在其中的位置。 两种情况之间的平均偏差为1.2%,最大差异为5.4%。

由于创建数百万个随机整数会花费大量时间,因此我选择仅对有序序列执行大多数基准测试,因此除非另有说明,否则与该情况有关。

基准代码本身可在GitHub上获得 。 要运行它,只需转到命令行,构建项目,然后执行生成的jar:

建立和运行基准

mvn clean install
java -jar target/benchmarks.jar

一些简单的调整:

  • 在执行调用的末尾添加正则表达式只会对完全限定名称与该表达式匹配的基准方法进行基准测试; 例如仅运行ControlStructuresBenchmark
    java -jar target/benchmarks.jar Control
  • AbstractIterationBenchmark上的注释控制执行每个基准测试的频率和时间
  • 常数NUMBER_OF_ELEMENTS定义要迭代的数组/列表的长度
  • 调整CREATE_ELEMENTS_RANDOMLY以在有序数或随机数数组之间切换
由Bart在CC-BY-NC-ND 2.0下发布。

发布时间由巴特下, CC-BY-NC-ND 2.0 。

流性能

重复实验

让我们从触发我写这篇文章的情况开始:在500'000个随机元素的数组中找到最大值。

SimpleOperationsBenchmark.array_max_for

int m = Integer.MIN_VALUE;
for (int i = 0; i < intArray.length; i++)if (intArray[i] > m)m = intArray[i];

我注意到的第一件事:我的笔记本电脑的性能比JAX文章所使用的机器好得多。 这是可以预料的,因为它被描述为“过时的硬件(双核,没有动态超频)”,但是它让我很高兴,因为我为这该死的东西花了足够的钱。 而不是0.36毫秒,而仅需0.130毫秒即可遍历整个阵列。 使用流查找最大值的结果更有趣:

SimpleOperationsBenchmark.array_max_stream

// article uses 'reduce' to which 'max' delegates
Arrays.stream(intArray).max();

Langer报告为此花费了5.35 ms的运行时间,与循环的0.36 ms相比,报告的运行速度降低了x15。 我始终测量大约560毫秒,因此最终导致“仅” x4.5变慢。 仍然很多。

接下来,本文将迭代列表与流式列表进行比较。

SimpleOperationsBenchmark.list_max_for

// for better comparability with looping over the array
// I do not use a "for each" loop (unlike the Langer's article);
// measurements show that this makes things a little faster
int m = Integer.MIN_VALUE;
for (int i = 0; i < intList.size(); i++)if (intList.get(i) > m)m = intList.get(i);

SimpleOperationsBenchmark.list_max_stream

intList.stream().max(Math::max);

for循环的结果是6.55毫秒,流的结果是8.33毫秒。 我的测量值为0.700毫秒和3.272毫秒。 尽管这会大大改变其相对性能,但会创建相同的顺序:

安吉利卡·兰格(Angelika Langer)
运作 时间(毫秒) 慢点 时间(毫秒) 慢点
array_max_for 0.36 0.123
array_max_stream 5.35 14'861% 0.599 487%
list_max_for 6.55 22% 0.700 17%
list_max_stream 8.33 27% 3.272 467%


我将遍历数组和列表的迭代之间的明显区别归因于拳击。 或更确切地说是间接导致的。 基本数组包含我们需要的值,但是列表由Integers数组支持,即,对必须首先解析的所需值的引用。

朗格和我的一系列相对变化之间的可观差异(+ 14'861%+ 22%+ 27%与+ 487%+ 17%+ 467%)强调了她的观点,即“流的性能模型并非微不足道的”。

最后,她的文章进行了以下观察:

我们只比较两个整数,在JIT编译后,它们几乎不止一个汇编指令。 因此,我们的基准测试说明了元素访问的成本–不一定是典型情况。 如果应用于序列中每个元素的功能是CPU密集型的,则性能指标将发生重大变化。 您会发现,如果功能受CPU的限制很大,则for循环流和顺序流之间将不再有可测量的差异。

因此,让我们锁定除整数比较之外的其他功能。

比较操作

我比较了以下操作:

  • max:求最大值。
  • sum:计算所有值的总和; 聚合为int而不考虑溢出。
  • 算术:为了对不太简单的数字运算建模,我将这些值与少量的移位和乘法相结合。
  • 字符串:为了模拟创建新对象的复杂操作,我将元素转换为字符串,然后逐个字符对其进行异或。

这些是结果(对于50万个有序元素;以毫秒为单位):

最高 算术
数组 清单 数组 清单 数组 清单 数组 清单
对于 0.123 0.700 0.186 0.714 4.405 4.099 49.533 49.943
0.559 3.272 1.394 3.584 4.100 7.776 52.236 64.989


这突显了真正的廉价比较,甚至加法花费的时间也要长50%。 我们还可以看到更复杂的操作如何使循环和流更紧密地联系在一起。 差异从几乎400%下降到25%。 同样,数组和列表之间的差异也大大减少了。 显然,算术和字符串运算受CPU限制,因此解析引用不会产生负面影响。

(不要问我为什么对数组元素进行流式运算要比在它们上循环要快。我已经将头撞墙了一段时间了。)

因此,让我们修复操作并了解迭代机制。

比较迭代机制

访问迭代机制的性能至少有两个重要变量:其开销以及是否导致装箱,这将损害内存绑定操作的性能。 我决定尝试通过执行CPU绑定操作来绕过拳击。 如上所述,算术运算可以在我的机器上实现。

迭代是通过for和for-each循环直接实现的。 对于流,我做了一些其他实验:

盒装和非盒装流

@Benchmark
public int array_stream() {// implicitly unboxedreturn Arrays.stream(intArray).reduce(0, this::arithmeticOperation);
}@Benchmark
public int array_stream_boxed() {// explicitly boxedreturn Arrays.stream(intArray).boxed().reduce(0, this::arithmeticOperation);
}@Benchmark
public int list_stream_unbox() {// naively unboxedreturn intList.stream().mapToInt(Integer::intValue).reduce(0, this::arithmeticOperation);
}@Benchmark
public int list_stream() {// implicitly boxedreturn intList.stream().reduce(0, this::arithmeticOperation);
}

在此,装箱和拆箱与数据的存储方式(在数组中拆箱并在列表中装箱)无关,而是与流如何处理值无关。

请注意, boxedIntStream (仅处理原始int的Stream的专用实现)转换为Stream<Integer> ,即对象上的流。 这将对性能产生负面影响,但程度取决于逃避分析的效果。

由于列表是通用的(即没有专门的IntArrayList ),因此它返回Stream<Integer> 。 最后一个基准测试方法调用mapToInt ,该方法返回一个IntStream 。 这是对流元素进行拆箱的幼稚尝试。

算术
数组 清单
对于 4.405 4.099
每次 4.434 4.707
流(未装箱) 4.100 4.518
流(盒装) 7.694 7.776


好吧,看那个! 显然,幼稚的拆箱确实有效(在这种情况下)。 我有一些模糊的概念,为什么可能会这样,但是我无法简洁(或正确)表达。 想法,有人吗?

(顺便说一句,所有关于装箱/拆箱和专门实现的讨论使我更加高兴的是Valhalla项目进展得如此之好 。)

这些测试的更具体的结果是,对于CPU限制的操作,流似乎没有相当大的性能成本。 在担心了很大的缺点之后,这很令人高兴。

比较元素数

通常,结果在序列长度不同(从50'000到50'000'000)的运行中都非常稳定。 为此,我检查了这些运行中每1'000'000个元素的归一化性能。

但是令我惊讶的是,随着序列的增加,性能不会自动提高。 我的想法很简单,即认为这将使JVM有机会应用更多优化。 相反,有一些明显的情况是性能实际上下降了:

从500'000到50000000个元素
方法 时间
array_max_for + 44.3%
array_sum_for + 13.4%
list_max_for + 12.8%


有趣的是,这些是最简单的迭代机制和操作。

胜者是比简单操作更复杂的迭代机制:

从500'000到50000000个元素
方法 时间
array_sum_stream – 84.9%
list_max_stream – 13.5%
list_sum_stream – 7.0%


这意味着我们在上面看到的500'000元素的表对于50'000'000看起来有点不同(归一化为1'000'000元素;以毫秒为单位):

最高 算术
数组 清单 数组 清单 数组 清单 数组 清单
500'000个元素
对于 0.246 1.400 0.372 1.428 8.810 8.199 99.066 98.650
1.118 6.544 2.788 7.168 8.200 15.552 104.472 129.978
50'000'000个元素
对于 0.355 1.579 0.422 1.522 8.884 8.313 93.949 97.900
1.203 3.954 0.421 6.710 8.408 15.723 96.550 117.690


我们可以看到算术字符串运算几乎没有变化。 但是事情发生了变化,因为最简单的最大求和运算需要更多的元素将字段更紧密地结合在一起。

反射

总而言之,我没有什么大的启示。 我们已经看到,循环和流之间的明显差异仅存在于最简单的操作中。 但是,令人惊奇的是,当我们涉及到数百万个元素时,差距正在缩小。 因此,在使用流时几乎不需要担心速度会大大降低。

但是,仍然存在一些未解决的问题。 最值得注意的是:并行流怎么样? 然后,我很想知道在哪种操作复杂度下,我可以看到从依赖于迭代(如summax )到独立于迭代(如算术 )性能的变化。 我也想知道硬件的影响。 当然,它会改变数字,但是在质量上也会有所不同吗?

对我来说,另一点是微基准测试并不是那么困难。 还是这样,我想除非有人指出我的所有错误…

翻译自: https://www.javacodegeeks.com/2015/09/stream-performance.html

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

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

相关文章

Redis-cluster集群【第一篇】:redis安装及redis数据类型

Redis介绍&#xff1a; 一、介绍redis 是一个开源的、使用C语言编写的、支持网络交互的、可以基于内存也可以持久化的Key-Value数据库。redis的源码非常简单&#xff0c;只要有时间看看谭浩强的C语言&#xff0c;在去看redis的源码能看懂50-60%。redis目前最大的集群应该是新浪…

ad10怎么挖铺的铜_黄金怎么验真假,简单易行方法多。

在当今社会&#xff0c;随着人们生活水平的提高&#xff0c;大家越来越追求生活的质量。也就形成了我们在生活中想通过各种细节丰富我们的生活&#xff0c;提升我们的生活情趣。导致我们对物质的追求也上了一个台阶&#xff0c;相比之下黄金市场的需求也大大增加。而我们大家的…

CMD启动mysql服务“发生系统错误 5”的解决办法

背景&#xff1a;cmd进入&#xff0c;启动mysql报&#xff1a;发生系统错误 5 问题&#xff1a; 解决办法&#xff1a; 找到cmd.exe的位置&#xff0c;不好找请使用以下命令。 找到文件&#xff0c;右键以管理员身份运行即可。

roads 构筑极致用户体验_坚持用户思维 推动领克汽车逆势突围

【2020年7月9日&#xff0c;杭州】近日&#xff0c;全球新高端品牌—领克公布了2020年6月销量数据。领克汽车6月实现月销量达13214辆&#xff0c;环比增长约2%&#xff0c;同比增长约53%&#xff0c;连续三个月获得双增长&#xff0c;并创下过去七个月以来最高月销量表现。2020…

微信小程序【WXSS 文件编译错误】unexpected “?“at pos 1的解决办法。

问题&#xff1a; 解决办法&#xff1a; 在控制台输入 openVendor() &#xff0c;清除里面的 wcsc wcsc.exe 然后重启工具。 如果涉及编码格式乱码的&#xff0c;请手动改为uft-8. 问题&#xff1a; 解决办法&#xff1a;

常见的一些威胁情报分析平台

在进行渗透过程中&#xff0c;也可以借助一些商用或者非商业平台来进行信息搜索及验证&#xff0c;主要针对邮箱、IP、域名、文件md5、apk文件等进行搜索&#xff0c;整理和完善线索信息。为了大家方便&#xff0c; 对目前公开的威胁情报平台进行搜集&#xff0c;在实际使用过…

CSDN博客专家申请的条件及规则

“博客专家”是CSDN给予质量较高、影响力较大的IT类博客的荣誉称号,代表了CSDN官方对其博客的肯定,同时博客专家也需要承担一定的社区责任。 申请规则:http://blog.csdn.net/experts/rule.html 申请地址:https://blog.csdn.net/experts/apply 申请条件: 注意事项: 专属福…

CVE-2022-22965:Spring Framework远程代码执行漏洞

CVE-2022-22965&#xff1a;Spring Framework远程代码执行漏洞 本文仅为验证漏洞&#xff0c;在本地环境测试验证&#xff0c;无其它目的 漏洞编号&#xff1a; CVE-2022-22965 漏洞说明&#xff1a; Spring framework 是Spring 里面的一个基础开源框架&#xff0c;其目的是…

CSS系列讲解-总目录

总目录: 欢迎来到孙叫兽的《CSS系列讲解》,蓝色字体为传送门,点击进入即可。本专栏已完结,大前端专栏支持更新。 玩转CSS系列: 什么是CSS?你真的理解? CSS页面DEMO CSS基本语法? 如何玩转CSS的Id 和 Class选择器? 怎么玩转CSS内部样式表与外部样式表? 怎么样才…

红队信息收集自动化工具-水泽(ShuiZe)

红队信息收集自动化工具-水泽&#xff08;ShuiZe&#xff09; 文章目录 红队信息收集自动化工具-水泽&#xff08;ShuiZe&#xff09;0x01 介绍0x02 安装0x03 效果展示0x04 POC编写0x05 使用方法0x06 实现原理Web -> 存活探测0x07 项目地址 0x01 介绍 定位&#xff1a;协助…

前端工程师应该达到什么水平,找工作薪资才比较高?

当然是水平越高&#xff0c;越容易找到工作&#xff0c;薪资越高在竞争这么激烈的2020年&#xff0c;就需要更加的努力&#xff0c;充实自己&#xff0c;让自己不被代替&#xff01;两条路&#xff1a;自学或者找培训班&#xff0c;找培训班的话&#xff0c;我推荐达内和传智播…

【转】使用JMeter对数据库做压力测试

作为一名开发人员&#xff0c;大多情况下都会认真的做好功能测试&#xff0c;但是却常常忽略了软件开发之后的压力测试&#xff0c;尤其是在面向大量用户同时使用的Web应用系统的开发过程&#xff0c;压力测试往往是不够充分的。近期我在一个求职招聘型的网站项目中就对压力测试…

孙叫兽进阶之路之Gitlab的使用(图文教程)

简介&#xff1a; GitLab是一个利用 Ruby on Rails 开发的开源应用程序&#xff0c;实现一个自托管的Git项目仓库&#xff0c;可通过Web界面进行访问公开的或者私人项目。 它拥有与Github类似的功能&#xff0c;能够浏览源代码&#xff0c;管理缺陷和注释。可以管理团队对仓库的…

PyQt4(使用ui)

1.使用qt designer设计界面&#xff0c;保存为test1.ui&#xff1a; 2.使用pyuic4 test1.ui -o ui.py生成ui代码。 3.程序载入。 import sys import ui from PyQt4 import QtCore, QtGuiclass MyWidget( QtGui.QWidget ):def __init__(self):super(MyWidget, self).__init__() …

孙叫兽进阶之路之源代码配置管理过程(图文教程)

简介:配置管理(Configuration Management,CM)是通过技术或行政手段对软件产品及其开发过程和生命周期进行控制、规范的一系列措施。配置管理的目标是记录软件产品的演化过程,确保软件开发者在软件生命周期中各个阶段都能得到精确的产品配置。

video 微信 标签层级过高_什么是微信小程序二级分销系统?如何玩转?

微信二级分销系统是通过帮助企业打造微分销商城&#xff0c;从店铺、商品、会员、分销、营销、数据分析等不同功能模块&#xff0c;让一个微信店铺焕发无限可能。微分销系统基于二级分销&#xff0c;以全员开店&#xff0c;以客推客模式迅速推动销量增长&#xff0c;快速招募微…

docker搭建简单的ctf题目

0x01 docker常用命令 1.拉取镜像。 docker pull [image] 2.查看docker当前镜像。 docker image ls 或 docker images 3.新建一个docker容器&#xff0c;并映射端口号。 docker run -d -p [host port]:[docker port] [image] 4.查看运行中的docker容器。 docker ps -a 5.进入一…

使用爱思助手备份苹果手机数据的方法

背景:前段时间刚给对象买的一个紫色的苹果11,128G的那种,最近发现电池电量忽然就少很多,电池除了点问题,去苹果售后店准备换一个电池,还在保修期,区分一下售后店(回厂修十多天)及专卖店(有备用电池)。今天提前备份一下数据,防止数据丢失,一般内存不大可以使用手机…

cmake 构建路径_新手必备:win10 系统下 VSCode+CMake+Clang+GCC 环境的搭建

打算用C/C把基本的数据结构与算法实现一遍, 为考研做准备, 因为只是想实现算法和数据结构, 就不太想用VisualStudio, 感觉VSCode不错, 遂在网上找了一些教程, 结合自己的需求, 配置一下开发环境。安装软件1、CMakeCMake是一个跨平台的自动化建构系统,它使用一个名为 CMakeLists…

网络安全单兵工具 -- YAKIT

网络安全单兵工具 -- YAKIT 一、下载及安装 1、原作者及下载地址 https://github.com/yaklang/yakit2、双击下载好的exe文件&#xff0c;点击核心引擎安装与升级 3、点击意见更新Yak引擎 4、以管理员启动 5、点击连接引擎 二、使用方法 1、扫描端口/指纹 2、爆破与未授权 3、专…