场景
Jmeter进行http接口压力测试:
https://www.cnblogs.com/badaoliumangqizhi/p/16301432.html
JMH
JMH,全称Java Microbenchmark Harness (微基准测试框架),是专门用于Java代码微基准测试的一套测试工具API,
是由Java虚拟机团队开发的的,一般用于代码的性能调优。
MicroBenchmark就是在method层面上的benchmark,精度可以精确到微秒级、甚至可以达到纳秒级别,
适用于 java 以及其他基于 JVM 的语言。与Apache JMeter 不同,JMH 测试的对象可以是任一方法,颗粒度更小,
而不仅限于接口以及API层面。
JMH比较典型的应用场景
想要知道某个函数需要执行多长时间,以及执行时间和输入之间的相关性
想要对比接口不同实现在给定条件下的吞吐量大小
想要知道百分之N的请求在多长时间内完成
想要找出了热点函数,需要对热点函数进行进一步优化时
针对于函数的多种实现方式(例如JSON序列化/反序列化有Jackson和Gson实现),不知道哪种实现性能更好
JMH官方仓库:
https://github.com/openjdk/jmh
JMH官网示例demo:
注:
博客:
https://blog.csdn.net/badao_liumang_qizhi
实现
1、首先如果jdk小于1.9,则需要添加依赖
<dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>1.23</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.23</version> </dependency>
2、参考上面官网提供helloworld的demo
package org.openjdk.jmh.samples; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; public class JMHSample_01_HelloWorld { @Benchmark public void wellHelloThere() { } public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(JMHSample_01_HelloWorld.class.getSimpleName()) .forks(1) .build(); new Runner(opt).run(); } }
可知需要新建类,并在main方法中这样写去启动基准测试和执行测试。
然后需要进行测试的方法添加@Benchmark注解。
3、按照上面官网提供的示例,对比ArrayList与LinkedList在头部进行添加时的性能测试添加一些细节
import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import java.util.ArrayList; import java.util.LinkedList; import java.util.concurrent.TimeUnit; //测试完成时间 @BenchmarkMode(Mode.AverageTime) //设置统计结果的时间单位 @OutputTimeUnit(TimeUnit.NANOSECONDS) //预热所需要配置的一些基本测试参数 @Warmup(iterations = 2,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间 @Measurement(iterations = 5,time = 5,timeUnit = TimeUnit.SECONDS) //fork一个线程 @Fork(1) //通过 State 可以指定一个对象的作用范围 @State(Scope.Thread) public class ArrayListWithLinkedListTestHeaderAdd { private static final int maxSize = 10000; //测试循环次数 private static final int operationSize = 100; //操作次数 private static ArrayList<Integer> arrayList; private static LinkedList<Integer> linkedList; public static void main(String[] args) throws RunnerException { //启动基准测试 Options opt = new OptionsBuilder() .include(ArrayListWithLinkedListTestHeaderAdd.class.getSimpleName()) //要导入的测试类 .build(); //执行测试 new Runner(opt).run(); } //@Setup作用于方法上,用于测试前的初始化工作 @Setup public void init(){ arrayList = new ArrayList<Integer>(); linkedList = new LinkedList<Integer>(); for (int i = 0; i < maxSize; i++) { arrayList.add(i); linkedList.add(i); } } //用于回收某些资源 @TearDown public void finish(){ } @Benchmark public void addArrayByFirst(Blackhole blackhole){ for (int i = 0; i < operationSize; i++) { arrayList.add(i,i); } //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题 blackhole.consume(arrayList); } @Benchmark public void addLinkedByFirst(Blackhole blackhole){ for (int i = 0; i < operationSize; i++) { linkedList.add(i,i); } //为了避免JIT忽略未被使用的结果计算/为了避免死码消除问题 blackhole.consume(linkedList); } }
这里加了一些注解,并配置了一些参数。
4、JMH部分注解说明
@Benchmark
需要测试的方法,添加该注解。
@BenchmarkMode
用来配置 Mode 选项,可用于类或者方法上,这个注解的 value 是一个数组,可以把几种 Mode 集合在一起执行,如:
@BenchmarkMode({Mode.SampleTime, Mode.AverageTime}),还可以设置为 Mode.All,即全部执行一遍。
Throughput:整体吞吐量,每秒执行了多少次调用,单位为 ops/time
AverageTime:用的平均时间,每次操作的平均时间,单位为 time/op
SampleTime:随机取样,最后输出取样结果的分布
SingleShotTime:只运行一次,往往同时把 Warmup 次数设为 0,用于测试冷启动时的性能
All:上面的所有模式都执行一次
@State
通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。
@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。
由于 JMH 允许多线程同时执行测试,不同的选项含义如下:
Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
Scope.Group:同一个线程在同一个 group 里共享实例
Scope.Thread:默认的 State,每个测试线程分配一个实例
@OutputTimeUnit
为统计结果的时间单位,可用于类或者方法注解
@Warmup
预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,
所以要让程序进行几轮预热,保证测试的准确性。参数如下所示:
iterations:预热的次数
time:每次预热的时间
timeUnit:时间的单位,默认秒
batchSize:批处理大小,每次操作调用几次方法
为什么需要预热?
因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,
所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。
@Measurement
实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和 @Warmup 相同。
@Threads
每个进程中的测试线程,可用于类或者方法上。
@Fork
进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。
@Param
指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,
使用该注解必须定义@State 注解。
@Setup
方法注解,会在执行 benchmark 之前被执行,正如其名,主要用于初始化。
@TearDown
方法注解,与@Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等
5、运行上面的main方法,对比测试两个方法的性能