JMH
- (一)、JMH概述
- (二)、JMH运用展示
- (三)、JMH注解
-
- 1.@Warmip和@Measurement (预热和真实执行)【类】
- 2.@BenchmarkMode和@OutputTimeUnit (输出方式和输出单位)【方法】
- 3.@State (状态)【类】
- 4.DefaultState (默认状态)【类】
- 5.@Setup和@TearDown (初始化和销毁)【方法】
- 6.FixtureLevel (初始化和销毁等级)【方法】
- 7.FixtureLevelInvocation (固定级别调用)
- 8.DeadCode (JVM调优)
- 9.Blackholes (黑洞拒绝JVM调优)
- 10.ConstantFold (常量折叠)
- 11.@OperationsPerInvocation (调用次数)
- 12.Loops (循环)
- 13.@fork (线程)
- 14.什么时候使用fork
- 15. @Group和@GroupThreads (组数和线程)
- (四)、JMH实战
(一)、JMH概述
1.什么是JMH?
JMH(Java Microbenchmark Harness)是一个 Java 工具,用于构建、运行和分析用 Java 和其他针对 JVM 的语言编写的 纳米/微米/毫/宏观 基准测试,而且是由Java虚拟机团队开发的。简单说,就是用来测量代码运行性能,简称 :“Java性能检测工具”。
JMH官网: https://openjdk.org/projects/code-tools/jmh/
JMH源码下载: https://github.com/openjdk/jmh
2.JMH入门操作
(1).没有JMH的时候我们怎么进行测试的?
我们利用结束的时间戳 - 结束后的时间戳
@Test
void contextLoads() {
long l1 = System.currentTimeMillis();
int n=0;
for (int i = 1; i <100000 ; i++) {
n+=1;
}
System.out.println(n);
long l2 = System.currentTimeMillis();
System.out.println("一共消耗时间: "+(l2-l1));
}
(2).利用JMH进行测试
添加依赖
<dependencies>
<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>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
</dependencies>
(二)、JMH运用展示
1.Hello JMH
源码
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() {
//
// this method was intentionally left blank.
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_01_HelloWorld.class.getSimpleName()) //执行测试方法的载体类是什么。
.forks(1) //总共测试几轮,默认我们呢选择1
.build();
new Runner(opt).run(); //执行
}
}
解释
(三)、JMH注解
1.@Warmip和@Measurement (预热和真实执行)【类】
@Warmip 预热
预热: 执行一次,一次1秒
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
真实测试执行一次,一次一秒
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
源码
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Warmup;
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.concurrent.TimeUnit;
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_01_HelloWorld {
@Benchmark //代表我们现在执行测试的就是这个空方法
public void wellHelloThere() {
//
// this method was intentionally left blank.
}
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();
}
}
2.@BenchmarkMode和@OutputTimeUnit (输出方式和输出单位)【方法】
源码
@BenchmarkMode(Mode.Throughput) //吞吐量测试, 输出报告: 每单位时间会执行多少次
@OutputTimeUnit(TimeUnit.SECONDS) //输出报告的时间单位
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_02_BenchmarkModes {
@Benchmark //执行测试的方法
@BenchmarkMode(Mode.Throughput) //吞吐量测试, 输出报告: 每单位时间会执行多少次
@OutputTimeUnit(TimeUnit.SECONDS) //输出报告的时间单位
public void measureThroughput() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime) //平均耗时测试, 输出报告每次操作耗时
@OutputTimeUnit(TimeUnit.SECONDS) //耗时的单位
public void measureAvgTime() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
@Benchmark
@BenchmarkMode(Mode.SampleTime) //抽样测试,输出报告: 会在执行过程中采样(每次操作耗时)
@OutputTimeUnit(TimeUnit.SECONDS)
public void measureSamples() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
@Benchmark
@BenchmarkMode(Mode.SingleShotTime) //冷启动测试,设置这个,此方法在一轮中只运行一次,这个模式主要是为了测试冷启动的性能
@OutputTimeUnit(TimeUnit.SECONDS) //输出单位
public void measureSingleShot() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
@Benchmark
@BenchmarkMode({
Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime}) // 前四种都进行测试。四种模式都会测试一次,输出四种报告
@OutputTimeUnit(TimeUnit.SECONDS)
public void measureMultiple() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
@Benchmark
@BenchmarkMode(Mode.All) // 不可以像第五种一样指定多种测试方法。只能测试全部四种
@OutputTimeUnit(TimeUnit.SECONDS)
public void measureAll() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(100);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_02_BenchmarkModes.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
第一个测试
第二个测试
第三个测试
第四次测试
第五次测试
第六次测试
3.@State (状态)【类】
源码
@State(Scope.Thread) //各个线程独自占有一个对象
@State(Scope.Benchmark) //整个测试总共用这一个对象
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
/**
* @State(Scope.XXXX) 描述了这个类对象的作用域
* 测试共享与独享的区别
*/
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_03_States {
// 所有测试线程各用各的 (独享)
@State(Scope.Thread) //各个线程独自占有一个对象
public static class ThreadState {
//在我们执行Benchmark的时候,他会生成一个ThreadState对象。可以被Benchmark直接使用的,也就是入参。
volatile double x = Math.PI;
}
// 根据main方法,会启动4个线程去一起执行
@Benchmark
public void measureUnshared(ThreadState state) {
//每一个线程的入参都是不同的对象
state.x++;
}
// 所有测试共享一个实列,用于测试有状态实列在多线程共享下的性能
// 一般用来测试多线程竞争下的性能
@State(Scope.Benchmark) //整个测试总共用这一个对象
public static class BenchmarkState {
volatile double x = Math.PI;
}
@Benchmark
public void measureShared(BenchmarkState state) {
//这个依然启动4个进程,但是入参都是同一个实列,竞争非常激烈
state.x++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_03_States.class.getSimpleName())
.threads(4) //在执行Benchmark的时候会启动4个线程
.forks(1)
.build();
new Runner(opt).run();
}
}
4.DefaultState (默认状态)【类】
源码
@State(Scope.Thread) //放在整体类上
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
@State(Scope.Thread) //我们标记这个类会成为一个对象由JMH进行管理。多个线程会创建多个下面类对象
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_04_DefaultState {
double x = Math.PI;
// 我们使用默认静态类的主要目的是: 我们不用特地的去写一个静态内部类去声明一个入参。如果没有上面的注解那么我们就需要声明一个静态内部类
@Benchmark
public void measure() {
x++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_04_DefaultState.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
5.@Setup和@TearDown (初始化和销毁)【方法】
启动之前准备工作
// 启动Benchmark之前的准备工作
@Setup // 必须在@State下的类中才能使用,实际上也算是@state管理对象的生命周期一部分
结束之后检查工作
// Benchmark结束之后的检查工作
@TearDown //必须在@State下的类中才能使用
源码
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
/**
* 控制state 对象中的方法在什么时候执行
*
* @Setup state中的方法如何被执行
* @TearDown state中的方法如何执行
* level.Trial 默认的执行策略,整个基准测试执行一次
*/
@State(Scope.Thread) //生命默认静态类,我们可以不用进行手动传入参数
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_05_StateFixtures {
double x;
// 启动Benchmark之前的准备工作
@Setup // 必须在@State下的类中才能使用,实际上也算是@state管理对象的生命周期一部分
public void prepare() {
x = Math.PI;
System.out.println("--------------1");
}
// Benchmark结束之后的检查工作
@TearDown //必须在@State下的类中才能使用
public void check() {
System.out.println("--------------2");
// 这里使用了断言
assert x > Math.PI : "Nothing changed?";
}
@Benchmark
public void measureRight() {
//正确代码,正常执行
x++;
}
@Benchmark
public void measureWrong() {
// 这里是错误代码实列,会在Benchmark执行完毕后报错
double x = 0;
x++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_05_StateFixtures.class.getSimpleName())
.forks(1)
.jvmArgs("-ea") //开启断言检测: assertion在一般情况下是关闭的,通过 java -ea 可以打开改功能,关闭为 -da
.build();
new Runner(opt).run();
}
}
6.FixtureLevel (初始化和销毁等级)【方法】
源码
@Setup(Level.Trial) // 与TearDown同理.启动之前会执行一次
@TearDown(Level.Trial) // 整个完整的基准测试之后才会执行一次
@TearDown(Level.Iteration) // 每轮循环完成之后才会执行一次
@TearDown(Level.Invocation) // 每次方法被调用都会执行一次
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
@State(Scope.Thread) //可以免除静态内部类
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 3,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_06_FixtureLevel {
double x;
// @Setup(Level.Trial) // 与TearDown同理.启动之前会执行一次
// @TearDown(Level.Trial) // 整个完整的基准测试之后才会执行一次
// @TearDown(Level.Iteration) // 每轮循环完成之后才会执行一次
// @TearDown(Level.Invocation) // 每次方法被调用都会执行一次
@TearDown(Level.Iteration) // 每次方法被调用都会执行一次
public void check() {
System.out.println("----------1");
assert x > Math.PI : "Nothing changed?";
}
@Benchmark
public void measureRight() {
x++;
}
@Benchmark
public void measureWrong() {
double x = 0;
x++;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_06_FixtureLevel.class.getSimpleName())
.forks(1)
.jvmArgs("-ea")
.shouldFailOnError(false) // 默认是false,即使assert错误也不会让整个测试失败
.build();
new Runner(opt).run();
}
}
7.FixtureLevelInvocation (固定级别调用)
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.*;
@OutputTimeUnit(TimeUnit.MICROSECONDS) // 报告时间单位
@BenchmarkMode(Mode.AverageTime) // 输出报告
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_07_FixtureLevelInvocation {
@State(Scope.Benchmark) // 共享一个对象
public static class NormalState {
ExecutorService service;
@Setup(Level.Trial) // 整个基准调用之前进行准备工作 (全局一次)
public void up() {
System.out.println("--------准备工作");
service = Executors.newCachedThreadPool();
}
@TearDown(Level.Trial) // 整个基准结束之后进行检查工作 (全局一次)
public void down() {
System.out.println("------------销毁");
service.shutdown();
}
}
public static class LaggingState extends NormalState {
public static final int SLEEP_TIME = Integer.getInteger("sleepTime", 10);
@Setup(Level.Invocation) //每次方法被调用都会执行
public void lag() throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(SLEEP_TIME); //调用的时候会睡眠10毫秒
}
}
@Benchmark
@BenchmarkMode(Mode.AverageTime) // 输出的方式是每次多少秒
public double measureHot(NormalState e, final Scratch s) throws ExecutionException, InterruptedException {
return e.service.submit(new Task(s)).get();
}
@Benchmark
@BenchmarkMode(Mode.AverageTime) //输出的方式是每次多少秒
public double measureCold(LaggingState e, final Scratch s) throws ExecutionException, InterruptedException {
return e.service.submit(new Task(s)).get();
}
@State(Scope.Thread) // 独享下面的静态方法
public static class Scratch {
private double p;
public double doWork() {
p = Math.log(p);
return p;
}
}
public static class Task implements Callable<Double> {
private Scratch s;
public Task(Scratch s) {
this.s = s;
}
@Override
public Double call() {
return s.doWork();
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_07_FixtureLevelInvocation.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
8.DeadCode (JVM调优)
源码
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
/*** 这个列子展示了一种场景: 有些代码会变成JVM优化掉,使得基准测试结果不可用。
*
* baseline(): 空方法
*
* measureWrong(): 由于计算结果并没有返回,JVM会自动优化,使其耗时测得与baseline()结果一样
*
* measureRight(): 将计算结果返回,JVM自动优化,这样才能真实测得真实的对象
*
*/
@State(Scope.Thread) //静态内部可省略
@BenchmarkMode(Mode.AverageTime) //每次多少秒
@OutputTimeUnit(TimeUnit.NANOSECONDS) //单位
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_08_DeadCode {
private double x = Math.PI;
private double compute(double d) {
for (int c = 0; c < 10; c++) {
d = d * d / Math.PI;
}
return d;
}
@Benchmark
public void baseline() {
// do nothing, this is a baseline
}
@Benchmark
public void measureWrong() {
// This is wrong: result is not used and the entire computation is optimized away.
compute(x); //因为我们没有使用计算结果,JVM会自动把这段代码优化掉,相当于测试了一个空方法。
}
@Benchmark
public double measureRight() {
// This is correct: the result is being used.
return compute(x); //让JVM不能优化掉,我们返回了结果
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_08_DeadCode.class.getSimpleName())
.forks(1)
.jvmArgs("-server") //注意这里一定要设置server模式,为了充分使用JVM调优。
.build();
new Runner(opt).run();
}
}
9.Blackholes (黑洞拒绝JVM调优)
黑洞: 可以有效的避免过于激进的优化
源码
@Benchmark
public void measureRight_2(Blackhole bh) {
// 如果执行结果不使用编译器优化
// 为了防止编译器自作主张,这里使用JMH提供的黑洞对象执行结果进行消费
bh.consume(compute(x1));
bh.consume(compute(x2));
}
package org.openjdk.jmh.samples;
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.concurrent.TimeUnit;
/**
* 空方法会被JVM给优化掉,但黑洞可以拒绝优化
*/
@BenchmarkMode(Mode.AverageTime) //输出方式
@OutputTimeUnit(TimeUnit.NANOSECONDS) //输出的时间
@State(Scope.Thread) //面内部静态
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_09_Blackholes {
double x1 = Math.PI;
double x2 = Math.PI * 2;
private double compute(double d) {
for (int c = 0; c < 10; c++) {
d = d * d / Math.PI;
}
return d;
}
@Benchmark
public double baseline() {
//整一个,且真实有效的只有一个
return compute(x1);
}
@Benchmark
public double measureWrong() {
//整两个,但真正有效值为1个
compute(x1); //编译器自动识别,直接被JVM优化掉
return compute(x2);
}
@Benchmark
public double measureRight_1() {
//整两个且真实计算的有2个
return compute(x1) + compute(x2);
}
@Benchmark
public void measureRight_2(Blackhole bh) {
// 如果执行结果不使用编译器优化
// 为了防止编译器自作主张,这里使用JMH提供的黑洞对象执行结果进行消费
bh.consume(compute(x1));
bh.consume(compute(x2));
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_09_Blackholes.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
10.ConstantFold (常量折叠)
常数折叠是编译器最佳化技术,被使用在现代的编译器中。进阶的常数传播形式,或称之为稀疏有条件的常量传播,可以更精确地传播常数及无缝的移除无用的程式码。
eg: i=20+40+30; 出现了常量折叠。那么编译器会直接省略步骤,直接给我们结果,而不会去在CPU中进行计算的操作,这就是省去无用代码。
源码
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_10_ConstantFold {
private double x = Math.PI; //变量
private final double wrongX = Math.PI; //常量
private double compute(double d) {
//计算方法
for (int c = 0; c < 10; c++) {
d = d * d / Math.PI;
}
return d;
}
@Benchmark
public double baseline() {
// 获取常量
// simply return the value, this is a baseline
return Math.PI;
}
@Benchmark
public double measureWrong_1() {
//计算常量
// This is wrong: the source is predictable, and computation is foldable.
return compute(Math.PI);
}
@Benchmark
public double measureWrong_2() {
// 计算常量
// This is wrong: the source is predictable, and computation is foldable.
return compute(wrongX);
}
@Benchmark
public double measureRight() {
//计算x x因为没有被final修饰,所以是变量
// This is correct: the source is not predictable.
return compute(x);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_10_ConstantFold.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
11.@OperationsPerInvocation (调用次数)
但报表只有一次
@OperationsPerInvocation(n)
告诉JMH的Benchmark一次执行相当于执行多少次,就是说JMH的Benchmark被JVM只
执行了一次,但在报表的时候会把这一次当作n次来计算。也就是输出这n次总耗时.
12.Loops (循环)
源码
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
/**
* 计算两个数相加耗时: 理论上应该一致,但我们发现for循环更快
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_11_Loops {
int x = 1;
int y = 2;
@Benchmark
public int measureRight() {
//测试变量相加
return (x + y);
}
private int reps(int reps) {
//进行循环相加
int s = 0;
for (int i = 0; i < reps; i++) {
s += (x + y);
}
return s;
}
@Benchmark
@OperationsPerInvocation(1) //告诉JMH的Benchmark一次执行相当于执行多少次,就是说JMH的Benchmark被JVM只执行了一次,但在报表的时候会把这一次当作n次来计算。也就是输出这n次总耗时
public int measureWrong_1() {
//循环相加 理论上应该与 第一个方式相加一样
return reps(1);
}
@Benchmark
@OperationsPerInvocation(10) //告诉JMH一次执行相当于执行多少次,就是说JMH执行n次,只不过在报表的时候只会报出n/n个
public int measureWrong_10() {
//循环相加
return reps(10);
}
@Benchmark
@OperationsPerInvocation(100)
public int measureWrong_100() {
//循环相加
return reps(100);
}
@Benchmark
@OperationsPerInvocation(1_000)
public int measureWrong_1000() {
//循环相加
return reps(1_000);
}
@Benchmark
@OperationsPerInvocation(10_000)
public int measureWrong_10000() {
//循环相加
return reps(10_000);
}
@Benchmark
@OperationsPerInvocation(100_000)
public int measureWrong_100000() {
//循环相加
return reps(100_000);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_11_Loops.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
13.@fork (线程)
线程 fork(0)
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
/**
* 我们发现进程pid是一样的,初步推测fork(0),代表没有进程新建
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_12_Forking {
@Benchmark
@Fork(0) //通过源码我们可以直到,0代表 no fork
public int measure_1_c1() {
return 1;
}
@Setup(Level.Trial) // 在基准执行之前先打印pid。
public void setup() {
printProcessID("setup");
}
public static void printProcessID(String name) {
//打印pid
System.out.println();
System.out.println("--------------");
System.out.println(name + " pid is : " + ManagementFactory.getRuntimeMXBean().getName());
System.out.println("--------------");
System.out.println();
}
public static void main(String[] args) throws RunnerException {
printProcessID("main"); // 执行main方法的时候打印pid
Options opt = new OptionsBuilder()
.include(JMHSample_12_Forking.class.getSimpleName())
.build();
new Runner(opt).run(); //当他启动的时候,才会开始运行基准
}
}
线程fork(1)
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
/**
* 我们发现进程pid是一样的,初步推测fork(1),代表新开一个进程
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_12_Forking {
@Benchmark
@Fork(1) //通过源码我们可以直到,1代表 1 fork
public int measure_1_c1() {
return 1;
}
@Setup(Level.Trial) // 在基准执行之前先打印pid。
public void setup() {
printProcessID("setup");
}
public static void printProcessID(String name) {
//打印pid
System.out.println();
System.out.println("--------------");
System.out.println(name + " pid is : " + ManagementFactory.getRuntimeMXBean().getName());
System.out.println("--------------");
System.out.println();
}
public static void main(String[] args) throws RunnerException {
printProcessID("main"); // 执行main方法的时候打印pid
Options opt = new OptionsBuilder()
.include(JMHSample_12_Forking.class.getSimpleName())
.build();
new Runner(opt).run(); //当他启动的时候,才会开始运行基准
}
}
线程 fork(10)
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
/**
* 我们发现进程pid是一样的,初步推测fork(10),代表新开10个进程
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_12_Forking {
@Benchmark
@Fork(10) //通过源码我们可以直到,10代表 10 fork
public int measure_1_c1() {
return 1;
}
@Setup(Level.Trial) // 在基准执行之前先打印pid。
public void setup() {
printProcessID("setup");
}
public static void printProcessID(String name) {
//打印pid
System.out.println();
System.out.println("--------------");
System.out.println(name + " pid is : " + ManagementFactory.getRuntimeMXBean().getName());
System.out.println("--------------");
System.out.println();
}
public static void main(String[] args) throws RunnerException {
printProcessID("main"); // 执行main方法的时候打印pid
Options opt = new OptionsBuilder()
.include(JMHSample_12_Forking.class.getSimpleName())
.build();
new Runner(opt).run(); //当他启动的时候,才会开始运行基准
}
}
fork() 默认值是-1,但效果等于传5
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.lang.management.ManagementFactory;
import java.util.concurrent.TimeUnit;
/**
* 我们发现进程pid是一样的,初步推测fork),代表新开5个进程
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_12_Forking {
@Benchmark
@Fork() //默认是-1 实际传5
public int measure_1_c1() {
return 1;
}
@Setup(Level.Trial) // 在基准执行之前先打印pid。
public void setup() {
printProcessID("setup");
}
public static void printProcessID(String name) {
//打印pid
System.out.println();
System.out.println("--------------");
System.out.println(name + " pid is : " + ManagementFactory.getRuntimeMXBean().getName());
System.out.println("--------------");
System.out.println();
}
public static void main(String[] args) throws RunnerException {
printProcessID("main"); // 执行main方法的时候打印pid
Options opt = new OptionsBuilder()
.include(JMHSample_12_Forking.class.getSimpleName())
.build();
new Runner(opt).run(); //当他启动的时候,才会开始运行基准
}
}
forking 源码
在同一个JVM中,不同的基准会相互影响的。
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
/**
* 在同一个JVM线程中,我们发现各个基准是相互影响的
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_12_Forking {
public interface Counter {
//匿名内部类
int inc();
}
public static class Counter1 implements Counter {
//实现接口1
private int x;
@Override
public int inc() {
return x++;
}
}
public static class Counter2 implements Counter {
//实现接口2
private int x;
@Override
public int inc() {
return x++;
}
}
public int measure(Counter c) {
//方法
int s = 0;
for (int i = 0; i < 10; i++) {
s += c.inc();
}
return s;
}
Counter c1 = new Counter1();
Counter c2 = new Counter2();
@Benchmark
@Fork(0)
public int measure_1_c1() {
//与main方法同进程
return measure(c1);
}
@Benchmark
@Fork(0)
public int measure_2_c2() {
//与main方法同进程
return measure(c2);
}
@Benchmark
@Fork(0)
public int measure_3_c1_again() {
//与main方法同进程
return measure(c1);
}
@Benchmark
@Fork(1)
public int measure_4_forked_c1() {
//与main方法不同进程
return measure(c1);
}
@Benchmark
@Fork(1)
public int measure_5_forked_c2() {
//与main方法不同进程
return measure(c2);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_12_Forking.class.getSimpleName())
.build();
new Runner(opt).run();
}
}
14.什么时候使用fork
大数样本: 大数法则当我们的样本足够多的时候,我们抽取的越多,那么就越接近平均值。 所以我们多开点线程就可以多取样本。
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
/**
* 大数样本: 大数法则当我们的样本足够多的时候,我们抽取的越多,那么就越接近平均值。
*/
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_13_RunToRun {
@State(Scope.Thread)
public static class SleepyState {
//定义一个静态内部类
public long sleepTime;
@Setup
public void setup() {
sleepTime = (long) (Math.random() * 1000);
System.out.println("---------------");
System.out.println("sleepTime-> "+sleepTime);
System.out.println("---------------");
} //生成一个随机休眠数
}
/*
* Now, we will run this different number of times.
*/
@Benchmark
@Fork(1)
public void baseline(SleepyState s) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(s.sleepTime);
}
@Benchmark
@Fork(5)
public void fork_1(SleepyState s) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(s.sleepTime);
}
@Benchmark
@Fork(20)
public void fork_2(SleepyState s) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(s.sleepTime);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_13_RunToRun.class.getSimpleName())
.warmupIterations(0)
.measurementIterations(3)
.build();
new Runner(opt).run();
}
}
15. @Group和@GroupThreads (组数和线程)
可以更加的接近生产环境
源码
@Group("g") // 假如这个注解里面的字符串相同,那就说明它们是一组
@GroupThreads(3) // 这个地方会创建三个线程进行执行
package org.openjdk.jmh.samples;
import org.openjdk.jmh.annotations.*;
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.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@State(Scope.Group)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class JMHSample_15_Asymmetric {
private AtomicInteger counter;
@Setup //基准初始化前
public void up() {
counter = new AtomicInteger(0);
}
@Benchmark
@Group("g") // 假如这个注解里面的字符串相同,那就说明它们是一组
@GroupThreads(1) // 这个地方会创建三个线程进行执行
public int inc() {
return counter.incrementAndGet(); //自增的操作
}
@Benchmark
@Group("g") // 假如这个注解里面的字符串相同,那就说明它们是一组
@GroupThreads(1) // 这个地方会创建三个线程进行执行
public int get() {
return counter.get(); //获取值的操作
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMHSample_15_Asymmetric.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
总的
Benchmark Mode Cnt Score Error Units
JMHSample_15_Asymmetric.g avgt 56.784 ns/op
JMHSample_15_Asymmetric.g:get avgt 26.845 ns/op
JMHSample_15_Asymmetric.g:inc avgt 66.764 ns/op
单独执行 get
Benchmark Mode Cnt Score Error Units
JMHSample_15_Asymmetric.g avgt 1.986 ns/op
单独执行inc 3个线程
Benchmark Mode Cnt Score Error Units JMHSample_15_Asymmetric.g avgt 40.330 ns/op
单独执行inc 1个线程
Benchmark Mode Cnt Score Error Units
JMHSample_15_Asymmetric.g avgt 6.659 ns/op
(四)、JMH实战
1.导入三个依赖
<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>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
2.比较冒泡排序和快速排序
package com.example.springboot01hello;
import org.junit.jupiter.api.Test;
import org.openjdk.jmh.annotations.*;
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 org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@SpringBootTest
@State(Scope.Thread)
@Warmup(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //预热次数和时间
@Measurement(iterations = 1,time = 1,timeUnit = TimeUnit.SECONDS) //测试次数和时间
public class SpringBoot01HelloApplicationTests {
int array[]={
1,2,5,3,6,9};
@Benchmark
public void wellHelloThere1() throws Exception {
sort(array);
}
@Benchmark
public void wellHelloThere2() throws Exception {
sort(array);
}
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
for (int i = 1; i < arr.length; i++) {
// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
boolean flag = true;
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
if (flag) {
break;
}
}
return arr;
}
public int[] sort1(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
// 从下标为1的元素开始选择合适的位置插入,因为下标为0的只有一个元素,默认是有序的
for (int i = 1; i < arr.length; i++) {
// 记录要插入的数据
int tmp = arr[i];
// 从已经排序的序列最右边的开始比较,找到比其小的数
int j = i;
while (j > 0 && tmp < arr[j - 1]) {
arr[j] = arr[j - 1];
j--;
}
// 存在比其小的数,插入
if (j != i) {
arr[j] = tmp;
}
}
return arr;
}
@Test
void contextLoads() throws RunnerException {
Options opt = new OptionsBuilder()
.include(SpringBoot01HelloApplicationTests.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
3.报表可视化
配置
Options opt = new OptionsBuilder()
.include(SpringBoot01HelloApplicationTests.class.getSimpleName())
.forks(1)
.resultFormat(ResultFormatType.JSON) //报表需要一个JSON文件
.result("/E:/加速器/result.json") //报表存放的位置
.build();
new Runner(opt).run();
可视化工具链接: https://jmh.morethan.io/
把生成的json文件拖拽到这里就可以可视化了。