当前位置:网站首页 > 技术博客 > 正文

jm tool



大家好,又见面了,我是你们的朋友全栈君。

关于JMH,可以直接查看官网地址

本博客内容来自我正在撰写的新书《Java性能优化(暂定名)》,也欢迎购买经典书

1.3.1 使用JMH

通过手工编写一个性能压测程序有较多的问题

JMH使用OPS来表示吞吐量,OPS,Opeartion Per Second,是衡量性能的重要指标,指得是每秒操作量。数值越大,性能越好。类似的概念还有TPS,表示每秒的事务完成量,QPS,每秒的查询量。 如果对每次执行时间进行升序排序,取出总数的99%的最大执行时间作为TP99的值,TP99通常是衡量系统性能重要指标,他表示99%的请求的响应时间不超过某个值。比TP99更严格的事TP999,要求99.9%的请求不超过某个值

有什么工具能帮助我们统计性能优化后的效果,比如更方便的统计OPS,TP99等。同时,我们为了做调优,不必每次都自己写一个测试程序

JMH,即Java Microbenchmark Harness,是专门用于代码微基准测试的工具套件。主要是基于方法层面的基准测试,精度可以达到纳秒级。当你定位到热点方法,希望进一步优化方法性能的时候,就可以使用JMH对优化的结果进行量化的分析。

JMH 实现了JSR269规范,即注解处理器,能在编译Java源码的时候,识别的到需要处理的注解,如@Beanmark,JMH能根据@Beanmark的配置生成一系列测试辅助类。关于JSR269,本书11章详细介绍. 流行开源Lombok 基于JSR269规范

开始是使用JMH,可以在工程里添加对JMH的依赖,添加如下

${jmh.version} 为jmh最新版本,为1.0

我们编写一个JMH测试类

MyBenchmark 有俩个需要比较的方法,都用 @Benchmark注解标识,MyBenchmark用了一系列注解,解释如下

我们在MyBenchmark添加需要的测试方法,如下

因为MyBenchmark包含了一个main方法,我们可以直接在IDE里直接运行这个方法,有如下输出

以上输出来自于我们的配置,第一行表示预热3次,每次执行1秒,第二行表示运行3次,每次运行5秒,这部分的运行结果计入统计。第三行表示1个线程执行,第四行统计性能数据纬度是Throughput,吞吐量

紧接着会运行testObjectKey方法,有如下输出

这里的Fork表示子进程,我们只配置里一个,因此只有一个进程的执行结果,该进程包含预热3次,每次1秒,以及运行3次,每次运行5秒,执行完testObjectKey方法后,会自动打印一个汇总信息

统计结果给出了多次测试后的最小值,最大值和均值,以及标准差 (stdev),置信区间(Confidence interval)

标准差(stdev)反映了数值相对于平均值得离散程度,置信区间是指由样本统计量所构造的总体参数的估计区间。在统计学中,一个概率样本的置信区间(Confidence interval)是对这个样本的某个总体参数的区间估计

testStringKey的输出与上面类似,这俩个比较方法执行完毕,会自动打印出一个性能对比数据表格

Benchmark列表示这次测试对比的方法,Mode列表上结果的统计纬度,Samples列表示采样次数,Samples=Fork*Iteration。Score是对这次评测的打分,对于testObjectKey,意味着他的OPS为每秒,大约4倍testStringKey方法

Score Error 这里表示性能统计上的误差,我们不需要关心这个数据,主要查看Score

可以修改统计纬度,比如修改为Mode.SampleTime,时间按照纳秒统计

可以看到有一组如下统计

可以看到90%的调用,是在2464纳秒内完成,99%的调用都是在4272纳秒完成的.

1.3.2 JMH常用设置

在这个例子,我们性能测试所依赖的对象areaService,perferAreaService 恰好是线程安全的,大多数时候性能测试方法都会引用一些外部实例对象,考虑到多线程测试访问这些实例对象,JMH要求必须为这些变量申明是Thread 内生效,还是整个BeanMark使用。如果是前者,JMH会为每个线程构建一个新的实例,后者则所有测试都共享这个变量,JMH用@State注解来说明对象的生命周期,@State注解作用在类上,比如,在MyBenchmark例子里,我们可以改成如下例子

作为参数传入每个待测试的方法。

也可以不使用内部类,直接使用申明性能测试的类,在类上使用@State注解

@Setup 和 @TearDown 是一对注解,作用于方法上,前者用于测试前的初始化工作,后者用于回收某些资源,比如压测前需要准备一些数据

@Level 用于控制 @Setup,@TearDown 的调用时机,有如下含义

JMH提供了Runner类能运行Benchmark类

include接受一个字符串表达式,表示需要测试的类和方法,如上例子测试所有方法MyBenchmark。如下例子则只测试方法名字包含“testObjectKey“的方法

OptionsBuilder包含了多个方法用于配置性能测试,可以指定循环次数,预热次数等,如下例子会用4个子进程做性能测试,每个进程预热一次,执行5次迭代

截至到目前为止,JMH都是通过一个main方法在IDE里执行,更为通常情况,JMH推荐使用单独的一个Maven工程来执行性能测试而不要放到业务工程里。可以通过maven archetype:generate 命令来生成一个心得JMH Maven工程。

为了阅读方便,分成几行,如上命令行应该放到一行执行,执行完毕后,生成了一个maven工程,maven工程仅仅包含了一个 MyBenchmark 例子。

我们可以修改MyBenchmark,添加我们需要测试的代码, 现在,可以创建一个性能测试的jar文件,通过运行如下maven命令

命令会在target目录下生成一个benchmarks.jar,包含了运行性能测试所需的任何东西,在命令行运行如下命令

JMH将会被启动,默认情况下运行MyBenchmark类里的所有被@Benchmark标注方法

有些性能测试需要了解不同输入参数的性能,比如对于模板引擎的性能测试中,考虑到字节流输出和字符流输出

JMH会分别赋值outpuType为1,2,3后,在各自测试一次,会输出如下

1.3.3 注意事项

编写JHM代码,需要考虑到虚拟机的优化,而使得测试失真,如下measureWrong代码就是所谓的Dead-Code代码

测试结果如下

在测试measureWrong方法,JIT能推测出方法体可以被优化调而不影响系统,measureRight因为定义了返回值,JIT不会优化。

下一个是关于常量折叠,JIT认为方法计算结果为常量,从而优化直接返回常量给调用者

如下是测试结果

考虑到inline对性能影响很大,JMH支持 @CompilerControl来控制是否允许内联

add和addInline方法都会调用dataAdd方法,前者使用CompilerControl类,可以用在方法或者类上,来提供编译选项

开发人员可能觉得上面的测试,add方法太简单,会习惯性的在add方法里方一个循环,以减少JMH调用add方法的成本。JMH不建议这么做,因为JIT会实际上对这种循环会做优化,以消除循环调用成本。如下是个例子可以看到循环测试结果不准确

注解OperationsPerInvocation 告诉JMH统计性能的时候需要做修正,比如@OperationsPerInvocation(10)调用了10次。

性能测试结果如下

可以看到,测试方法里使用循环,会促使JIT进行优化,做循环消除(参考第8章JIT TODO)

1.3.4 单元测试

无论是编写JMH,或者其他性能测试程序,好习惯是先编写一个单元测试用例,以确保性能测试方法的准确性,对于1.3.4的Inline类,可以先编写一个单元测试用例,确保add和addInline返回正确结果

在JMH工程调用maven install 生成测试代码的时候,会进行单元测试,从而保证测试结果的准确

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/157397.html原文链接:https://javaforall.cn

  • 上一篇: uvm基本概念
  • 下一篇: monkey测试结果
  • 版权声明


    相关文章:

  • uvm基本概念2025-06-06 23:01:05
  • 单片机指针用法2025-06-06 23:01:05
  • python课程有用吗2025-06-06 23:01:05
  • vmstat -p2025-06-06 23:01:05
  • 同步fifo verilog2025-06-06 23:01:05
  • monkey测试结果2025-06-06 23:01:05
  • select查询语句大全2025-06-06 23:01:05
  • mysql分区分表原理2025-06-06 23:01:05
  • 数据库索引的弊端2025-06-06 23:01:05
  • 命名实体识别算法2025-06-06 23:01:05