性能测试流程_流性能

性能测试流程

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

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

总览

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

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

序幕

免责声明

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

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

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

系统

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

基准测试

捷运

基准测试是使用JVM性能团队本身开发和使用的Java微基准测试线束(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);
}

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

请注意, boxed将IntStream (仅处理原始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限制的操作,流似乎没有相当大的性能成本。 在担心了很大的缺点之后,这很令人高兴。

比较元素数

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

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

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


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

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

从500'000到50'000'000个元素
方法 时间
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


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

反射

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

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

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

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

性能测试流程

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

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

相关文章

C++vector用法总结

点击蓝字关注我们来源自网络&#xff0c;侵删一.vector1. vector 说明1&#xff09;vector是C标准模板库中的部分内容&#xff0c;它是一个多功能的&#xff0c;能够操作多种数据结构和算法的模板类和函数库。2.&#xff09;vector之所以被认为是一个容器&#xff0c;是因为它能…

python中创建集合的语句_Python 集合(set) 介绍

集合 set集合是可变的容器集合内的数据对象都是唯一的(不能重复多次的)集合是无序的存储结构&#xff0c;集合中的数据没有先后关系集合内的元素必须是不可变对象集合是可迭代对象(可以用for等遍历)集合是相当于只有键&#xff0c;没有值的字典(键则是集合的数据)创建空集合&am…

C++ STL 线性容器的用法

点击蓝字关注我们来源于网络&#xff0c;侵删1.vectorvector 是顺序容器的一种&#xff0c;是可变长的动态数组&#xff0c;支持随机访问迭代器&#xff0c;所有stl算法都能对 vector 进行操作。vector 容器在实现时&#xff0c;动态分配的存储空间一般都大于存放元素所需的空间…

redis复制_Redis复制

redis复制本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#xff0c…

python打开.data_DataX初体验-python命令启动以及纯java启动

一、DataX安装官网描述很详细&#xff0c;很简单---->DataX安装二、使用示例&#xff1a;从Oracle数据库导数据到Mysql数据库事先准备&#xff1a;Oracle数据库的ORDER_INFO表&#xff0c;MySQL数据库的order_info表&#xff0c;表结构相同2.1使用Python启动生成模板文件打开…

java高级教程_高级Java教程

java高级教程课程大纲 学习Java基础很容易。 但是&#xff0c;真正钻研该语言并研究其更高级的概念和细微差别将使您成为一名出色的Java开发人员。 网络上充斥着“软”&#xff0c;“便宜”&#xff0c;“低端” Java教程&#xff0c;但是所缺少的实际上是将您带入新的高度的材…

JavaWeb笔记之WEB项目

一. 版本控制 版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理&#xff0c;是软件配置管理的核心思想之一。 版本控制最主要的功能就是追踪文件的变更。它将什么时候、什么人更改了文件的什么内容等信息忠实地了记录下来。每一次文件的改变&a…

c++获取数组长度

点击蓝字关注我们来源于网络&#xff0c;侵删方法一&#xff1a; 用宏函数 #define#define foo(arr) sizeof(arr)/sizeof(arr[0])int main(){int arr[4] {1,2,3,4};cout<<foo(arr)<<endl; }方法二&#xff1a;用函数模板int getArrLen1(int *a ){return sizeof(a)…

python从列表随机取出多个数据_【python】从数组随机取数据

在神经网络中&#xff0c;经常会用到批量样本训练。我们需要从数组随机取数据&#xff0c;主要有以下几种方法&#xff1a;1、np.random.shuffle&#xff1a;将原数组打乱import numpy as nparray np.random.randint(1,100,size10)#[63 32 80 33 61 45 28 55 39 80]batch_size…

java代理模式_Java代理

java代理模式本文是我们名为“ 高级Java ”的学院课程的一部分。 本课程旨在帮助您最有效地使用Java。 它讨论了高级主题&#xff0c;包括对象创建&#xff0c;并发&#xff0c;序列化&#xff0c;反射等。 它将指导您完成Java掌握的旅程&#xff01; 在这里查看 &#xff01;…

如何用python抢课_名额不够,技术来凑,利用Python实现教务系统强制性抢课

这个不是一个点击脚本&#xff0c;而是属于扩容性质的脚本。名额不够咱们利用技术来解决&#xff01;最近一学期一次的抢课大戏又来了&#xff0c;几家欢乐几家愁。O(∩_∩)O哈哈~(l我每次一选就过了hah&#xff0c;我还是有欧的时候滴)。看着他们盯着教务系统就着急&#xff0…

C++ 利用硬件加速矩阵乘法

点击蓝字关注我们来源于网络&#xff0c;侵删1.矩阵乘法定义2.矩阵类封装我们用 C封装了一个n m 的矩阵类&#xff0c;用二维数组来存储数据&#xff0c;定义如下&#xff1a;#define MAXN 1000 #define LL __int64class Matrix { private:int n, m;LL** pkData; public:Matri…

redis分片_Redis分片

redis分片本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#xff0c…

python tkinter窗口切换_tkinter--实现简单的页面切换

[Python] 纯文本查看 复制代码import tkinter as tkindex_num 4def up_page():info frame_root.place_info()x int(info[x]) 100y info[y]frame_root.place(xx, yy)global index_numindex_num 1index[text] "第 {} 页".format( index_num)def down_page():inf…

解析C++全排列

点击蓝字关注我们来源于网络&#xff0c;侵删1.C实现全排列的函数next_permutation(start,end)这个函数在暴力解决问题方面有很大作用&#xff0c;使用时需要引入头文件 < algorithm >&#xff0c;当当前序列不存在下一个序列时就会结束&#xff0c;若想得到一个序列的全…

python读写文件代码_Python 读写文件的操作代码

Python读写文件模式1、r 打开只读文件&#xff0c;该文件必须存在。2、r 打开可读写的文件&#xff0c;该文件必须存在。3、w 打开只写文件&#xff0c;若文件存在则文件长度清为0&#xff0c;即该文件内容会消失。若文件不存在则建立该文件。4、w 打开可读写文件&#xff0c;若…

redis开启redis_Redis聚类

redis开启redis本文是我们学院课程的一部分&#xff0c;标题为Redis NoSQL键值存储 。 这是Redis的速成课程。 您将学习如何安装Redis和启动服务器。 此外&#xff0c;您还会在Redis命令行上乱七八糟。 接下来是更高级的主题&#xff0c;例如复制&#xff0c;分片和集群&#…

C++ 读取文件操作

点击蓝字关注我们来源于网络&#xff0c;侵删1.先上代码&#xff1a;#include <fstream> #include<iostream> using namespace std;//文本文件读文件 void test01() {//1、包含头文件//2、创建流对象ifstream ifs;//3、打开文件并且判断是否打开成功ifs.open("…

python调用simulink_使用Python从dbc文件中提取simulink建模数据定义

使用dbc文件建模完成CAN通讯是一种比较高效的开发模式&#xff0c;不过在建模的过程中dbc文件中描述的数据需要自己去定义。使用文本编辑工具打开dbc文件可以看到&#xff0c;实际上dbc文件是一个可以进行语义解析的文本。这样&#xff0c;通过脚本语言便可以轻松的实现simulin…

cov/cor中有遗漏值_协调遗漏的效果–使用简单的NIO客户端/服务器测量回送延迟...

cov/cor中有遗漏值在这篇文章中&#xff0c;我演示了许多想法和技术&#xff1a; 如何编写一个简单的非阻塞NIO客户端/服务器 协同遗漏的影响 如何测量百分位数的延迟&#xff08;相对于简单平均&#xff09; 如何在计算机上计时延迟回送 我最近正在为客户端服务器应用程序…