103.【Java Microbenchmark Harness】

news/2024/10/30 17:20:26/

JMH

  • (一)、JMH概述
    • 1.什么是JMH?
    • 2.JMH入门操作
        • (1).没有JMH的时候我们怎么进行测试的?
        • (2).利用JMH进行测试
  • (二)、JMH运用展示
    • 1.Hello 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实战
    • 1.导入三个依赖
    • 2.比较冒泡排序和快速排序
    • 3.报表可视化

(一)、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的时候我们怎么进行测试的?

我们利用结束的时间戳 - 结束后的时间戳

    @Testvoid 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();  //执行}}

解释

v
在这里插入图片描述

(三)、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个线程去一起执行@Benchmarkpublic void measureUnshared(ThreadState state) {  //每一个线程的入参都是不同的对象state.x++;}// 所有测试共享一个实列,用于测试有状态实列在多线程共享下的性能// 一般用来测试多线程竞争下的性能@State(Scope.Benchmark)  //整个测试总共用这一个对象public static class BenchmarkState {volatile double x = Math.PI;}@Benchmarkpublic 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;// 我们使用默认静态类的主要目的是:  我们不用特地的去写一个静态内部类去声明一个入参。如果没有上面的注解那么我们就需要声明一个静态内部类@Benchmarkpublic 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?";}@Benchmarkpublic void measureRight() {  //正确代码,正常执行x++;}@Benchmarkpublic 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?";}@Benchmarkpublic void measureRight() {x++;}@Benchmarkpublic 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;}@Overridepublic 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;}@Benchmarkpublic void baseline() {// do nothing, this is a baseline}@Benchmarkpublic void measureWrong() {// This is wrong: result is not used and the entire computation is optimized away.compute(x);  //因为我们没有使用计算结果,JVM会自动把这段代码优化掉,相当于测试了一个空方法。}@Benchmarkpublic 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调优)

黑洞: 可以有效的避免过于激进的优化

源码

 @Benchmarkpublic 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;}@Benchmarkpublic double baseline() {  //整一个,且真实有效的只有一个return compute(x1);}@Benchmarkpublic double measureWrong() { //整两个,但真正有效值为1个compute(x1);  //编译器自动识别,直接被JVM优化掉return compute(x2);}@Benchmarkpublic double measureRight_1() {  //整两个且真实计算的有2个return compute(x1) + compute(x2);}@Benchmarkpublic 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;}@Benchmarkpublic double baseline() {  // 获取常量// simply return the value, this is a baselinereturn Math.PI;}@Benchmarkpublic double measureWrong_1() {  //计算常量// This is wrong: the source is predictable, and computation is foldable.return compute(Math.PI);}@Benchmarkpublic double measureWrong_2() {  // 计算常量// This is wrong: the source is predictable, and computation is foldable.return compute(wrongX);}@Benchmarkpublic 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;@Benchmarkpublic 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 forkpublic int measure_1_c1() {return 1;}@Setup(Level.Trial)  // 在基准执行之前先打印pid。public void setup() {printProcessID("setup");}public static void printProcessID(String name) {  //打印pidSystem.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方法的时候打印pidOptions 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 forkpublic int measure_1_c1() {return 1;}@Setup(Level.Trial)  // 在基准执行之前先打印pid。public void setup() {printProcessID("setup");}public static void printProcessID(String name) {  //打印pidSystem.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方法的时候打印pidOptions 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 forkpublic int measure_1_c1() {return 1;}@Setup(Level.Trial)  // 在基准执行之前先打印pid。public void setup() {printProcessID("setup");}public static void printProcessID(String name) {  //打印pidSystem.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方法的时候打印pidOptions 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 实际传5public int measure_1_c1() {return 1;}@Setup(Level.Trial)  // 在基准执行之前先打印pid。public void setup() {printProcessID("setup");}public static void printProcessID(String name) {  //打印pidSystem.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方法的时候打印pidOptions 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 { //实现接口1private int x;@Overridepublic int inc() {return x++;}}public static class Counter2 implements Counter {  //实现接口2private int x;@Overridepublic 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;@Setuppublic 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};@Benchmarkpublic void wellHelloThere1() throws Exception {sort(array);}@Benchmarkpublic 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;}@Testvoid 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文件拖拽到这里就可以可视化了。
在这里插入图片描述

在这里插入图片描述


http://www.ppmy.cn/news/33056.html

相关文章

SDIO读写SD卡速度有多快?

前两天测试了SPI方式读写SD卡的速度《SPI方式读写SD卡速度测试》&#xff0c;今天来测试一下SDIO方式的读写速度。测试条件&#xff1a;单片机&#xff1a;STM32F407VET6编译环境&#xff1a;MDK 5.30HAL库SD卡&#xff1a;闪迪32GB/64GB TF卡文件系统&#xff1a;FatFS R0.12c…

Python 异步: 在非阻塞子进程中运行命令(19)

动动发财的小手&#xff0c;点个赞吧&#xff01; 我们可以从 asyncio 执行命令。该命令将在我们可以使用非阻塞 I/O 写入和读取的子进程中运行。 1. 什么是 asyncio.subprocess.Process asyncio.subprocess.Process 类提供了由 asyncio 运行的子进程的表示。它在 asyncio 程序…

flutter调用go

文章目录命令引入greeting.aar和使用android中使用Flutter2gopluginPlugin.kt参考文档命令 mkdir demo cd demo go mod init demo 编写greeting.go go env -w GOPROXYhttps://goproxy.cn go install golang.org/x/mobile/cmd/gomobilelatest gomobile init go get golang.org/x…

第九章:C语言数据结构与算法初阶之堆

系列文章目录 文章目录系列文章目录前言一、堆的定义二、堆的实现三、堆的接口函数1、初始化2、销毁3、插入4、删除5、判空6、元素个数四、堆排序1、建堆2、排序五、堆的应用——TOPK1、什么是TOPK问题&#xff1f;2、解决方法总结前言 堆就是完全二叉树。 一、堆的定义 我们…

Springboot新手开发 Cloud篇

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;后端专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

URLConnection/JNI

目录 URLConnection SSRF JNI安全基础 构建动态链接库供Java调用 JNI-定义native方法 JNI-生成类头文件 JNI-编写C/C本地命令执行实现 主函数调用 URLConnection 在java中&#xff0c;java抽象出来了一个URLConnection类&#xff0c;它用来表示应用程序以及与URL建立通…

进程和线程的区别和联系

进程和线程的区别和联系1. 认识线程2. 进程和线程的关系3. 进程和线程的区别4. 线程共享了进程哪些资源1. 上下文切换2. 线程共享了进程哪些资源1.代码区2. 数据区3. 堆区1. 认识线程 线程是进程的一个实体,它被包含在进程中,一个进程至少包含一个线程,一个进程也可以包含多个…

[ 系统安全篇 ] 拉黑IP - 火绒安全软件设置IP黑名单 windows使用系统防火墙功能设置IP黑名单

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…