性能测试流程_流性能

性能测试流程

当我阅读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);
}

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

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

比较元素数

通常,结果在序列长度不同(从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


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

反射

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

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

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

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

性能测试流程

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

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

相关文章

C++vector用法总结

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

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…

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)…

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…

解析C++全排列

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

C/C++,判断变量的类型

点击蓝字关注我们来源于网络&#xff0c;侵删出于某个奇葩需求&#xff0c;研究了一下c/c如何判断变量类型&#xff0c;整理总结在此&#xff0c;分享给大家&#xff0c;也避免自己以后绕弯。一、c判断变量类型c中&#xff0c;可以利用typeid()来判断变量类型。第一步&#xff…

c/c++语言实现登陆界面

点击蓝字关注我们来源自网络&#xff0c;侵删一.整体功能介绍实现一个登陆界面1 输出一个登陆界面2 用户名能够实现邮箱验证&#xff0c;regex库&#xff0c;密码要不可见3 进度条的模拟实现4 音乐播放二.分步实现1.输出一个登陆界面首先对此功能使用到的函数进行简单的介绍。s…

spark restful_Spark入门:也可以用Java创建轻量级的RESTful应用程序

spark restful最近&#xff0c;我一直在使用Spark &#xff08;一种Java的Web框架&#xff0c;与Apache Spark 不相关&#xff09;编写RESTful服务。 当我们计划写这篇文章时&#xff0c;我已经做好了不可避免的接口&#xff0c;样板代码和深层层次结构的Java风格的准备。 令我…

C++的get()函数与getline()函数使用详解

点击蓝字关注我们来源自网络&#xff0c;侵删一.C的get()函数使用详解1.C get()函数get()函数是cin输入流对象的成员函数&#xff0c;它有3种形式&#xff1a;无参数的&#xff1b;有一个参数的&#xff1b;有3个参数的。1) 无参数的其调用形式为cin.get()用来从指定的输入流中…

电脑所有程序里有不一样颜色_12个好玩的电脑屏保,让你成为别人眼中最靓的仔。...

Hello 大家好&#xff0c;这里是工具狂人。作为一个靠打字(哦不&#xff0c;搬砖)为生的新媒体小编&#xff0c;每天多数时候都是对着电脑屏幕&#xff0c;中途有时会拿起手机回复消息、查看短信、刷起微博。刷手机的时间一长&#xff0c;眼前的电脑会自动打开系统的屏保程序&a…

java8 函数式编程_如何使用Java 8函数式编程生成字母序列

java8 函数式编程我偶然发现了用户“ mip”一个有趣的堆栈溢出问题 。 问题是&#xff1a; 我正在寻找一种生成字母序列的方法&#xff1a; A, B, C, ..., Z, AA, AB, AC, ..., ZZ.可以很快将其识别为Excel电子表格的标题&#xff0c;它确实做到了&#xff1a; 到目前为止&a…

C++判断变量/对象/枚举类型的简单方式

点击蓝字关注我们来源于网络&#xff0c;侵删1.关键点<typeinfo>使用typeid()操作符所需包含的头文件。typeid()获取变量类型信息的操作符&#xff0c;其返回值类型为std::typeinfo。我们可使用typeid(n) typeid(int)的方式来判断变量n是否为类型int。注&#xff1a;可以…

C++ 空指针和野指针

点击蓝字关注我们来源于网络&#xff0c;侵删1.空指针指针变量指向内存中编号为0的空间为空指针。空指针指向的内存空间是不可以访问的 。代码&#xff1a;#include<iostream> using namespace std; int main() {int a 10;int * p &a;cout << p << end…

sap abap开发从入门到精通_SAP开发-ABAP数据字典(锁)

企业级软件或开发框架&#xff0c;必然支持后台高并发&#xff0c;即支持多人同时访问数据库。SAP作为资深企业管理软件&#xff0c;自然也不例外&#xff0c;ABAP可以很方便的开发出支持高并发的程序&#xff0c;要实现高并发&#xff0c;正确使用锁对象是其中一个重要环节&am…

(acm)C++加速输入的几种方法

点击蓝字关注我们来源于网络&#xff0c;侵删1.CIO流的同步和绑定在C中&#xff0c;cin和cout的速度其实不并不慢&#xff0c;C中的流的IO速度相当的快&#xff0c;其速度与初始设定的缓存区大小和硬盘的IO速度有关。但在C中&#xff0c;为了兼容C的IO(scanf和printf)&#xff…