更准确的测试Java程序性能——JMH基准测试
什么是JMH
JMH,即Java Microbenchmark Harness,Java平台下的一套微基准测试工具。如果我们需要测试API性能的话,就可以用上这个工具,所以它并不是取代单元测试的。它可以在开发阶段提供性能参考标准,不过这并不代表线上的性能表现,不同的硬件和操作系统也会带来性能差异,所以最终还是需要上到测试或沙箱环境,让测试人员进行压测。
为什么需要JMH
在了解JMH之前,如果需要性能测试,我们通常会使用for循环,或者JMeter。而JMH正是比for循环严谨,比JMeter使用简单的测试工具。
再者,不知道你注意过没有,在使用for循环测试时,第一次或者头几次运行总是最慢的,越到后面越快。从《计算机组成与设计 硬件软件接口》一书中可以了解到,从更底层讲,Java是解释型的语言。虽然Java也需要编译,但是编译后只是字节码,还需要JVM解释成对应宿主机的机器码。解释的优势是可移植性,但是性能较差。在20世纪80和90年代,虽然解释型语言的性能也飞速提升,但是与C语言相比,仍有10倍的性能差距。
为了保持可移植性,同时又提高性能,Java便开发了即时编译器(Just In Time complier),其通过记录运行的程序来找到所谓的“热点”方法,然后将它们直接编译成宿主机的指令序列,即不通过JVM解释那一层。这样以后该方法的运行就会更快。
看到这里也就明白了,为什么程序越到后面就会越快。JMH在真正的测试之前会预热程序,而且还可以通过配置进程数、线程数等参数来使程序更接近实际的运行状况。
如何使用
首先引入Maven依赖:
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
<scope>test</scope>
</dependency>
本案例中,我写了一个简单的小程序,它会从指定目录读取文件夹内容(每行一个数字),然后会对取出来的数字进行排序。排序算法选择了插入排序和归并排序,我们通过基准测试来看看两者的性能差距。
读取文件内容
public class ReadFile {
public static int[] readInteger(String path){
try(BufferedReader in = new BufferedReader(new FileReader(path));) {
List<Integer> temp = new ArrayList<>();
String str;
while ((str = in.readLine()) != null) {
temp.add(Integer.parseInt(str));
}
int[] result = new int[temp.size()];
for(int i=0;i<temp.size();i++){
result[i]=temp.get(i);
}
return result;
} catch (Exception e) {
e.printStackTrace();
return new int[0];
}
}
}
两个排序算法就不贴了,网上可以搜到很多。实际的开发可能会用上SpringBoot,所以还得与Junit整合,并使用自动注入功能。先直接贴上测试代码:
@BenchmarkMode(Mode.All)
@Warmup(iterations = 3)//预热轮数
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Threads(8)//线程数
@Fork(0) //fork的次数,如果想用Autowired自动注入,这个填0
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@RunWith(SpringRunner.class) //整合SpringBoot的测试运行环境
@SpringBootTest
public class JHMTest {
//想用自动注入功能,对象必须是静态的,fork填0
private static SortService service;
@Autowired
void setService(SortService service){
JHMTest.service =service;
}
@Test
public void executeBenchmark() throws RunnerException {
//JMH的选项配置,除了上面的注解方式的配置,也可以直接在这个Options里面配置。
//其中/Users/xxxx/Desktop/Benchmark.json是结果的输出文件
Options options = new OptionsBuilder().include(this.getClass().getSimpleName())
.output("/Users/xxxx/Desktop/Benchmark.json").build();
new Runner(options).run();
}
@Benchmark
public void insertSortTest(){
int[] arr = ReadFile.readInteger("/Users/xxxxx/Desktop/test.txt");
service.insertSort(arr);
}
@Benchmark
public void mergeSortTest(){
int[] arr = ReadFile.readInteger("/Users/xxxxx/Desktop/test 2.txt");
service.mergeSort(arr);
}
}
上面注释简单写了几个关键点,我们执行executeBenchmark方法,JMH就会执行该类下带有Benchmark注解的方法。最终结果会输出到指定文件中。
其他注解的解释可见图

截图截取自:https://dunwu.github.io/javatech/test/jmh.html#jmh-api
结果查看
打开结果文件,前面一大坨是系统信息,可以简单看看,直接拉到最后,结果如下:
Benchmark Mode Cnt Score Error Units
JHMTest.insertSortTest thrpt 129.302 ops/ms
JHMTest.mergeSortTest thrpt 122.224 ops/ms
JHMTest.insertSortTest avgt 0.065 ms/op
JHMTest.mergeSortTest avgt 0.066 ms/op
JHMTest.insertSortTest sample 122410 0.066 ± 0.002 ms/op
JHMTest.insertSortTest:insertSortTest·p0.00 sample 0.014 ms/op
JHMTest.insertSortTest:insertSortTest·p0.50 sample 0.050 ms/op
JHMTest.insertSortTest:insertSortTest·p0.90 sample 0.106 ms/op
JHMTest.insertSortTest:insertSortTest·p0.95 sample 0.120 ms/op
JHMTest.insertSortTest:insertSortTest·p0.99 sample 0.192 ms/op
JHMTest.insertSortTest:insertSortTest·p0.999 sample 0.492 ms/op
JHMTest.insertSortTest:insertSortTest·p0.9999 sample 11.891 ms/op
JHMTest.insertSortTest:insertSortTest·p1.00 sample 17.334 ms/op
JHMTest.mergeSortTest sample 122055 0.066 ± 0.002 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.00 sample 0.014 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.50 sample 0.050 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.90 sample 0.107 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.95 sample 0.121 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.99 sample 0.187 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.999 sample 0.457 ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.9999 sample 11.957 ms/op
JHMTest.mergeSortTest:mergeSortTest·p1.00 sample 12.419 ms/op
JHMTest.insertSortTest ss 0.020 ms/op
JHMTest.mergeSortTest ss 0.020 ms/op
结果如上,Mode中thrpt代表吞吐量,单位时间内的执行次数。avgt是平均时间,一次执行需要的单位时间。sample是基于采样的执行时间,采样频率由JMH自动控制。ss是单次执行的时间。
从结果上看,两种排序算法的性能相差无几,当然与我们的逻辑太简单也有关系。这次的分享就到这里,大家赶紧用到自己的项目中,测试一下吧。
更准确的测试Java程序性能——JMH基准测试的更多相关文章
- Java程序性能定位工具-火焰图
Java程序性能定位工具-火焰图 前言 Java火焰图是一种新的查看CPU利用率方式.今天就带大家一起使用来自Google大神的工具来生成火焰图.火焰图非常的直观,问题一目了然,希望有一天它能成为JA ...
- Java程序性能优化技巧
Java程序性能优化技巧 多线程.集合.网络编程.内存优化.缓冲..spring.设计模式.软件工程.编程思想 1.生成对象时,合理分配空间和大小new ArrayList(100); 2.优化for ...
- 《Java程序性能优化:让你的Java程序更快、更稳定》
Java程序性能优化:让你的Java程序更快.更稳定, 卓越网更便宜,不错的书吧
- [JAVA] java程序性能优化
一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...
- Java程序性能优化——让你的java程序更快、更稳定
1.Java性能调优概述 1.1.Web服务器,响应时间.吞吐量是两个重要的性能参数. 1.2.程序性能的几个表现: 执行速度:程序的反映是否迅速,响应时间是否足够短 内存分配:分配是否合理,是否过多 ...
- Java程序性能优化读书笔记(一):Java性能调优概述
程序性能的主要表现点: 执行速度:程序的反映是否迅速,响应时间是否足够短 内存分配:内存分配是否合理,是否过多地消耗内存或者存在内存泄漏 启动时间:程序从运行到可以正常处理业务需要花费多少时间 负载承 ...
- Java程序性能优化之性能概述
性能的基本概念 一).什么叫程序的性能? 程序运行所需的内存和时间. 二).性能的表现形式: 1).执行速度: 程序的反应是否迅速,响应时间是否足够短. 2).启动时间:程序从运行到可以处理正常业务所 ...
- java程序性能优化
一.避免在循环条件中使用复杂表达式 在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快. 例子: import java.util ...
- 《Java程序性能优化》学习笔记 设计优化
豆瓣读书:http://book.douban.com/subject/19969386/ 第一章 Java性能调优概述 1.性能的参考指标 执行时间: CPU时间: 内存分配: 磁盘吞吐量: 网络吞 ...
随机推荐
- list切片
对list进行切片 取一个list的部分元素是非常常见的操作.比如,一个list如下: >>> L = ['Adam', 'Lisa', 'Bart', 'Paul'] 取前3个元素 ...
- CAS 的ABA 问题
CAS CAS:Compare and Swap, 翻译成比较并交换. java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁. 其原理是CAS有 ...
- 交互式查询⼯具Impala
Impala是什么: Impala是Cloudera提供的⼀款开源的针对HDFS和HBASE中的PB级别数据进⾏交互式实时查询(Impala 速度快),Impala是参照⾕歌的新三篇论⽂当中的Drem ...
- Java | 个人总结的Java常用API手册汇总
目录 常用API JavaAPI 1 java.lang String StringBuilder Integer parseXxx Math Object System Throwable Thre ...
- 🏆【JVM技术专区】「难点-核心-遗漏」TLAB内存分配+锁的碰撞(技术串烧)!
JVM内存分配及申请过程 当使用new关键字或者其他任何方式进行创建一个类的对象时,JVM虚拟机需要为该对象分配内存空间,而对象的大小在类加载完成后已经确定了,所以分配内存只需要在Java堆中划分出一 ...
- springboot系列总结(一)---初识springboot
Spring Boot是一个简化Spring开发的框架.用来监护spring应用开发,约定大于配置,去繁就简,just run 就能创建一个独立的,产品级的应用. 一说springboot ,Java ...
- SpringMVC笔记(2)
一.SpringMVC的数据响应 1.1 数据响应方式 1.1.1 页面跳转 直接返回字符串 将返回的字符串与内部资源视图解析器的前后缀拼接 进行访问(默认为转发) 通过ModelAndView对象返 ...
- 基于Linux的系统排错
1.系统引导过程概述 2.系统异常及恢复 [1]grub系统引导 1)mbr上446字节丢失 模拟问题: dd if=/dev/zero? of=/dev/vda? bs=446? count=1? ...
- 20210811 Dove 打扑克,Cicada 与排序,Cicada 拿衣服
考场 开考感觉 T3 比较可做.T1 看上去不难但毫无思路. 先想了 25min T3,想到一个确定左端点,二分最长的右端点,甚至想到了用猫树维护区间 or and...上厕所回来发现假了,没有单调性 ...
- MacOS安装和卸载Java
安装java 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 设 ...