JMH,全称 Java Microbenchmark Harness (微基准测试框架)
在日常开发中,我们对一些代码的调用或者工具的使用会存在多种选择方式,在不确定他们性能的时候,我们首先想要做的就是去测量它。大多数时候,我们会简单的采用多次计数的方式来测量,来看这个方法的总耗时。
但是,如果熟悉JVM类加载机制的话,应该知道JVM默认的执行模式是JIT编译与解释混合执行。JVM通过热点代码统计分析,识别高频方法的调用、循环体、公共模块等,基于JIT动态编译技术,会将热点代码转换成机器码,直接交给CPU执行。
也就是说,JVM会不断的进行编译优化,这就使得很难确定重复多少次才能得到一个稳定的测试结果?所以,很多有经验的同学会在测试代码前写一段预热的逻辑。
JMH,全称 Java Microbenchmark Harness (微基准测试框架),是专门用于Java代码微基准测试的一套测试工具API,是由 OpenJDK/Oracle 官方发布的工具。何谓 Micro Benchmark 呢?简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。
Java的基准测试需要注意的几个点:
- 测试前需要预热。
- 防止无用代码进入测试方法中。
- 并发测试。
- 测试结果呈现。
JMH的使用场景:
- 定量分析某个热点函数的优化效果
- 想定量地知道某个函数需要执行多长时间,以及执行时间和输入变量的相关性
- 对比一个函数的多种实现方式
demo
依赖:
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>${jmh.version}</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>${jmh.version}</version> <scope>provided</scope> </dependency>
这里我以测试LinkedList 通过index 方式迭代和foreach 方式迭代的性能差距为例子,编写测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | @State (Scope.Benchmark) @OutputTimeUnit (TimeUnit.SECONDS) @Threads (Threads.MAX) public class LinkedListIterationBenchMark { private static final int SIZE = 10000 ; private List<String> list = new LinkedList<>(); @Setup public void setUp() { for ( int i = 0 ; i < SIZE; i++) { list.add(String.valueOf(i)); } } @Benchmark @BenchmarkMode (Mode.Throughput) public void forIndexIterate() { for ( int i = 0 ; i < list.size(); i++) { list.get(i); System.out.print( "" ); } } @Benchmark @BenchmarkMode (Mode.Throughput) public void forEachIterate() { for (String s : list) { System.out.print( "" ); } } } |
IDE中测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * 仅限于IDE中运行 * 命令行模式 则是 build 然后 java -jar 启动 * <p> * 1. 这是benchmark 启动的入口 * 2. 这里同时还完成了JMH测试的一些配置工作 * 3. 默认场景下,JMH会去找寻标注了@Benchmark的方法,可以通过include和exclude两个方法来完成包含以及排除的语义 */ public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(LinkedListIterationBenchMark. class .getSimpleName()) // 可以用方法名,也可以用XXX.class.getSimpleName() .forks( 3 ) // forks(3)指的是做3轮测试,因为一次测试无法有效的代表结果,所以通过3轮测试较为全面的测试,而每一轮都是先预热,再正式计量。 .warmupIterations( 2 ) // 预热2轮 .measurementIterations( 2 ) // 代表正式计量测试做2轮,而每次都是先执行完预热再执行正式计量, 内容都是调用标注了@Benchmark的代码。 .output( "D:/Benchmark.log" ) .build(); new Runner(opt).run(); } |
测试结果:
Benchmark Mode Cnt Score Error Units
LinkedListIterationBenchMark.forEachIterate thrpt 2 481.343 ops/s
LinkedListIterationBenchMark.forIndexIterate thrpt 2 65.249 ops/s
注解介绍
@BenchmarkMode
微基准测试类型。JMH 提供了以下几种类型进行支持:
类型 | 描述 |
---|---|
Throughput | 每段时间执行的次数,一般是秒 |
AverageTime | 平均时间,每次操作的平均耗时 |
SampleTime | 在测试中,随机进行采样执行的时间 |
SingleShotTime | 在每次执行中计算耗时 |
All | 所有模式 |
@Warmup
这个单词的意思就是预热,iterations = 3
就是指预热轮数。
@Measurement
正式度量计算的轮数。
iterations
进行测试的轮次time
每轮进行的时长timeUnit
时长单位
@Threads
每个进程中的测试线程。
@Fork
进行 fork 的次数。如果 fork 数是3的话,则 JMH 会 fork 出3个进程来进行测试。
@OutputTimeUnit
基准测试结果的时间类型。一般选择秒、毫秒、微秒。
@Benchmark
方法级注解,表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test
类似。
@Param
属性级注解,@Param
可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。
@Setup
方法级注解,这个注解的作用就是我们需要在测试之前进行一些准备工作,比如对一些数据的初始化之类的。
@TearDown
方法级注解,这个注解的作用就是我们需要在测试之后进行一些结束工作,比如关闭线程池,数据库连接等的,主要用于资源的回收等。
@State
当使用@Setup
参数的时候,必须在类上加这个参数,不然会提示无法运行。
就比如我上面的例子中,就必须设置state
。
State
用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为三种。
- Thread: 该状态为每个线程独享。
- Group: 该状态为同一个组里面所有线程共享。
- Benchmark: 该状态在所有线程间共享。
参考:juejin.cn/post/6844903936869007368
更多的example可以参考官方给出的JMH samples (https://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
2017-02-10 jQuery ajax读取本地json文件