JMH实战

引言

性能调优一直是工作中很重要的必会技能,如何知晓自己写代码的优劣呢?当然是看代码运行时间,时间越短,说明代码越优。

不太严谨的方法1:

long startTime = System.currentTimeMillis();
long endTime = System.currentTimeMillis();
long elapsed = endTime - startTime

不太严谨的方法2:

Stopwatch stopwatch = Stopwatch.createStarted(); 
long duration = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);

在代码的运行过程中,JVM可能会对其进行运行时的优化,比如循环展开、运行时编译等,这样会导致某组未经优化的性能数据参与统计计算。

JMH是一种严谨的比较准确地测量出代码性能的工具。

JMH实战

JMH简介

基准测试是指通过设计科学的测试方法、测试工具和测试系统,实现对一类测试对象的某项性能指标进行定量的和可对比的测试。 而JMH是一个用来构建,运行,分析Java或其他运行在JVM之上的语言的 纳秒/微秒/毫秒/宏观 级别基准测试的工具。

当定位到热点方法,希望进一步优化方法性能的时候,就可以使用 JMH 对优化的结果进行量化的分析。

JMH 比较典型的应用场景如下:

  1. 想准确地知道某个方法需要执行多长时间,以及执行时间和输入之间的相关性
  2. 对比接口不同实现在给定条件下的吞吐量
  3. 查看多少百分比的请求在多长时间内完成

JMH基本概念和配置

1、模式(Mode)

Mode表示JMH的测量方式和角度,共有四种。具体解释见附录。

2、迭代(Iteration)

迭代是JMH的一次测量单位。可以设置每次迭代执行的时间,在这个时间段会不间断的调用被测方法,并采样计算吞吐量、平均时间等。

3、预热(Warmup)

由于Java虚拟机JIT的存在,同一个方法在JIT编译前后的时间将会不同。通常只考虑方法在JIT编译之后的性能。预热测试不会作为最终的统计结果。预热的目的是让 JVM 对被测代码进行足够多的优化

4、状态(State)

通过State可以指定一个对象的作用范围。范围主要有两种,一种为线程范围,也就是一个对象只会被一个线程访问。在多线程进行测试时,会为每一个线程生成一个对象。另一种是基准测试范围(BenchMark),即多个线程共享一个实例。

JMH导入

因为 JMH 是 JDK9 自带的,如果是 JDK9 之前的版本需要加入如下依赖


<dependency>
    <groupId>org.openjdk.jmhgroupId>
    <artifactId>jmh-coreartifactId>
    <version>1.36version>
dependency>


<dependency>
    <groupId>org.openjdk.jmhgroupId>
    <artifactId>jmh-generator-annprocessartifactId>
    <version>1.36version>
 
dependency>

JMH示例

示例 1:

public class Sample1 {

    @Benchmark
    public void wellHelloThere() {
        // this method was intentionally left blank.
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Sample1.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

输出:

# JMH version: 1.36
# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13
# VM invoker: D:\Program Files\Java\jdk1.8.0_181\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\lib\idea_rt.jar=56615:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: demon.research.jmh.Sample1.wellHelloThere

# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration   1: 3043964132.598 ops/s
# Warmup Iteration   2: 3101902277.414 ops/s
# Warmup Iteration   3: 3095154844.054 ops/s
# Warmup Iteration   4: 3071397273.708 ops/s
# Warmup Iteration   5: 3046919646.879 ops/s
Iteration   1: 2492869078.352 ops/s
Iteration   2: 1888390724.003 ops/s
Iteration   3: 2037507524.756 ops/s
Iteration   4: 3100584563.561 ops/s
Iteration   5: 3082318955.953 ops/s


Result "demon.research.jmh.Sample1.wellHelloThere":
  2520334169.325 ±(99.9%) 2183136692.483 ops/s [Average]
  (min, avg, max) = (1888390724.003, 2520334169.325, 3100584563.561), stdev = 566953666.190
  CI (99.9%): [337197476.842, 4703470861.808] (assumes normal distribution)


# Run complete. Total time: 00:01:41

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.

Benchmark                Mode  Cnt           Score            Error  Units
Sample1.wellHelloThere  thrpt    5  2520334169.325 ± 2183136692.483  ops/s

示例 2:


public class Sample2 {

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public void measureThroughput() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(100);
    }

    @Benchmark
    @BenchmarkMode(Mode.AverageTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void measureAvgTime() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(100);
    }

    @Benchmark
    @BenchmarkMode(Mode.SampleTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void measureSamples() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(100);
    }

    @Benchmark
    @BenchmarkMode(Mode.SingleShotTime)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void measureSingleShot() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(100);
    }

    @Benchmark
    @BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime, Mode.SingleShotTime})
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void measureMultiple() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(100);
    }

    @Benchmark
    @BenchmarkMode(Mode.All)
    @OutputTimeUnit(TimeUnit.MICROSECONDS)
    public void measureAll() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(100);
    }


    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Sample2.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

附录

参考

示例:https://hg.openjdk.org/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/

JMH注解

@AuxCounters

@Benchmark

指示方法需要测试。

@BenchmarkMode

@Inherited
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BenchmarkMode {
    Mode[] value();
}
public enum Mode {
    Throughput("thrpt", "Throughput, ops/time"),
    AverageTime("avgt", "Average time, time/op"),
    SampleTime("sample", "Sampling time"),
    SingleShotTime("ss", "Single shot invocation time"),
    All("all", "All benchmark modes");
}   

指明了基准测试的模式。这些模式可以自由组合,甚至可以使用全部。

  • Mode.Throughput :吞吐量,单位时间内执行的次数
  • Mode.AverageTime:平均时间,一次执行需要的单位时间,其实是吞吐量的倒数
  • Mode.SampleTime:是基于采样的执行时间,采样频率由JMH自动控制,同时结果中也会统计出p90p95的时间
  • Mode.SingleShotTime:单次执行时间,只执行一次,可用于冷启动的测试
  • Mode.All:所有模式依次运行

@CompilerControl

@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CompilerControl {

    Mode value();

    /**
     * 编译模式 
     */
    enum Mode {

        /**
         * Insert the breakpoint into the generated compiled code.
         */
        BREAK("break"),

        /**
         * Print the method and it's profile.
         */
        PRINT("print"),

        /**
         * Exclude the method from the compilation.
         */
        EXCLUDE("exclude"),

        /**
         * Force inline.
         */
        INLINE("inline"),

        /**
         * Force skip inline.
         */
        DONT_INLINE("dontinline"),

        /**
         * Compile only this method, and nothing else.
         */
        COMPILE_ONLY("compileonly"),;

        private final String command;
 
    }

}

可以控制是否使用内联。

  • CompilerControl.Mode.DONT_INLINE:不使用内联
  • CompilerControl.Mode.INLINE:强制使用内联
  • CompilerControl.Mode.EXCLUDE:不编译

@Fork

@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Fork {

    int BLANK_FORKS = -1;

    String BLANK_ARGS = "blank_blank_blank_2014";

    /** @return number of times harness should fork, zero means "no fork" */
    int value() default BLANK_FORKS;

    /** @return number of times harness should fork and ignore the results */
    int warmups() default BLANK_FORKS;

    /** @return JVM executable to run with */
    String jvm() default BLANK_ARGS;

    /** @return JVM arguments to replace in the command line */
    String[] jvmArgs() default { BLANK_ARGS };

    /** @return JVM arguments to prepend in the command line */
    String[] jvmArgsPrepend() default { BLANK_ARGS };

    /** @return JVM arguments to append in the command line */
    String[] jvmArgsAppend() default { BLANK_ARGS };

}

进行 fork 的次数,可用于类或者方法上。如果 fork 数是 2 的话,则 JMH 会 fork 出两个进程来进行测试。

@Group

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Group {
    String value() default "group";
}

@Group 定义了一个线程组

@GroupThreads

@Inherited
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GroupThreads {

    /** @return number of threads to run the concrete method with. */
    int value() default 1;

}

@GroupThreads 可以分配线程给测试用例,可以测试线程执行不均衡的情况

@Measurement

实际调用方法所需要配置的一些基本测试参数,可用于类或者方法上,参数和 @Warmup 相同。

@OperationsPerInvocation

@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationsPerInvocation {

    /**
     * @return Number of operations per single {@link Benchmark} call.
     */
    int value() default 1;

}

@OutputTimeUnit

可以指定输出的时间单位,可以传入 java.util.concurrent.TimeUnit 中的时间单位,最小可以到纳秒级别;

如果在一个测试中指定了多种测试模式,给定的时间单位将用于所有的测试

@Param

@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Param {

    String BLANK_ARGS = "blank_blank_blank_2014";
    String[] value() default { BLANK_ARGS };

}

指定某项参数的多种情况,特别适合用来测试一个函数在不同的参数输入的情况下的性能,只能作用在字段上,使用该注解必须定义 @State 注解

@Setup

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Setup {
    Level value() default Level.Trial;
}
public enum Level {
 	//Benchmark级别
    Trial,
 	//执行迭代级别
    Iteration,
 	//每次方法调用级别
    Invocation,
}

用于基准测试前的初始化动作。

@State

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface State {
    Scope value();
}

public enum Scope {
    Benchmark,
    Group,
    Thread,
}

通过 State 可以指定一个对象的作用范围,JMH 根据 scope 来进行实例化和共享操作。@State 可以被继承使用,如果父类定义了该注解,子类则无需定义。由于 JMH 允许多线程同时执行测试,不同的选项含义如下:

  • Scope.Benchmark:所有测试线程共享一个实例,测试有状态实例在多线程共享下的性能
  • Scope.Group:同一个线程在同一个 group 里共享实例
  • Scope.Thread:默认的 State,每个测试线程分配一个实例

@TearDown

用于基准测试后的动作。

@Threads

@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Threads {

    /**
     * The magic value for MAX threads.
     * This means Runtime.getRuntime().availableProcessors() threads.
     */
    int MAX = -1;

    /**
     * @return Number of threads; use Threads.MAX to run with all available threads.
     */
    int value();

}

每个进程中的测试线程数量,可用于类或者方法上。 默认是Runtime.getRuntime().availableProcessors()

@Timeout

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Timeout {
    int time();
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

每次迭代的超时时间。如果超过此时间,则退出。

@Warmup

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Warmup {

    int BLANK_ITERATIONS = -1;
    int BLANK_TIME = -1;
    int BLANK_BATCHSIZE = -1;

    /** @return Number of warmup iterations */
    int iterations() default BLANK_ITERATIONS;

    /** @return Time for each warmup iteration */
    int time() default BLANK_TIME;

    /** @return Time unit for warmup iteration duration */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /** @return batch size: number of benchmark method calls per operation */
    int batchSize() default BLANK_BATCHSIZE;

}

预热所需要配置的一些基本测试参数,可用于类或者方法上。一般前几次进行程序测试的时候都会比较慢,所以要让程序进行几轮预热,保证测试的准确性。参数如下所示:

  • iterations:预热的次数
  • time每次预热的时间
  • timeUnit:时间的单位,默认秒
  • batchSize:批处理大小,每次操作调用几次方法

为什么需要预热?
因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译为机器码,从而提高执行速度,所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。

JMH option 编码设置

JMH使用OptionsBuilder设置options。

        Options opt = new OptionsBuilder()
                .include(Sample2.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();

option

  • include:benchmark 所在的类的名字,注意这里是使用正则表达式对所有类进行匹配的。

默认值

public class Defaults {
    /* 预热迭代次数 */
    public static final int WARMUP_ITERATIONS = 5;

    /**
     * Number of warmup iterations in {@link org.openjdk.jmh.annotations.Mode#SingleShotTime} mode.
     */
    public static final int WARMUP_ITERATIONS_SINGLESHOT = 0;

    /**
     * The batch size in warmup mode.
     */
    public static final int WARMUP_BATCHSIZE = 1;

    /**
     * The duration of warmup iterations.
     */
    public static final TimeValue WARMUP_TIME = TimeValue.seconds(10);
 
    /*  MEASUREMENT迭代次数 */
    public static final int MEASUREMENT_ITERATIONS = 5;

    /**
     * Number of measurement iterations in {@link org.openjdk.jmh.annotations.Mode#SingleShotTime} mode.
     */
    public static final int MEASUREMENT_ITERATIONS_SINGLESHOT = 1;

    /**
     * The batch size in measurement mode.
     */
    public static final int MEASUREMENT_BATCHSIZE = 1;

    /**
     * The duration of measurement iterations.
     */
    public static final TimeValue MEASUREMENT_TIME = TimeValue.seconds(10);

    /**
     * Number of measurement threads.
     */
    public static final int THREADS = 1;

    /**
     * Number of forks in which we measure the workload.
     */
    public static final int MEASUREMENT_FORKS = 5;

    /**
     * Number of warmup forks we discard.
     */
    public static final int WARMUP_FORKS = 0;

    /**
     * Should JMH fail on benchmark error?
     */
    public static final boolean FAIL_ON_ERROR = false;

    /**
     * Should JMH synchronize iterations?
     */
    public static final boolean SYNC_ITERATIONS = true;

    /**
     * Should JMH do GC between iterations?
     */
    public static final boolean DO_GC = false;

    /**
     * The default {@link org.openjdk.jmh.results.format.ResultFormatType} to use.
     */
    public static final ResultFormatType RESULT_FORMAT = ResultFormatType.CSV;

    /**
     * Default prefix of the result file.
     */
    public static final String RESULT_FILE_PREFIX = "jmh-result";

    /**
     * Default {@link org.openjdk.jmh.runner.options.WarmupMode}.
     */
    public static final WarmupMode WARMUP_MODE = WarmupMode.INDI;

    /**
     * Default {@link org.openjdk.jmh.runner.options.VerboseMode}.
     */
    public static final VerboseMode VERBOSITY = VerboseMode.NORMAL;

    /**
     * Default running mode.
     */
    public static final Mode BENCHMARK_MODE = Mode.Throughput;

    /**
     * Default output time unit.
     */
    public static final TimeUnit OUTPUT_TIMEUNIT = TimeUnit.SECONDS;

    /**
     * Default operations per invocation.
     */
    public static final Integer OPS_PER_INVOCATION = 1;

 
    //默认持续时间  10min
    public static final TimeValue TIMEOUT = TimeValue.minutes(10);

    /**
     * Default benchmarks to include.
     */
    public static final String INCLUDE_BENCHMARKS = ".*";

}

运行

1、IDE 运行:Debug执行main,抛出异常

ERROR: transport error 202: connect failed: Connection refused
ERROR: JDWP Transport dt_socket failed to initialize, TRANSPORT_INIT(510)
JDWP exit error AGENT_ERROR_TRANSPORT_INIT(197): No transports initialized [debugInit.c:750]

需要使用Run模式。

2、命令行执行

2.1、先打包为Jar文件

2.2、运行命令

java -jar jmh-demo.jar  -rf json -rff result.json
  • -rf json 是输出 json的格式
  • -rff /data/result.json 是输出文件位置和名称

3、输出

# JMH version: 1.36
# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13
# VM invoker: D:\Program Files\Java\jdk1.8.0_181\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\lib\idea_rt.jar=56615:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each                                #预热5次迭代, 每次迭代 10 s。
# Measurement: 5 iterations, 10 s each                           #5次迭代, 每次迭代 10 s。
# Timeout: 10 min per iteration                                  # 每次迭代 10 min。
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: demon.research.jmh.Sample1.wellHelloThere

# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration   1: 3043964132.598 ops/s
# Warmup Iteration   2: 3101902277.414 ops/s
# Warmup Iteration   3: 3095154844.054 ops/s
# Warmup Iteration   4: 3071397273.708 ops/s
# Warmup Iteration   5: 3046919646.879 ops/s
Iteration   1: 2492869078.352 ops/s
Iteration   2: 1888390724.003 ops/s
Iteration   3: 2037507524.756 ops/s
Iteration   4: 3100584563.561 ops/s
Iteration   5: 3082318955.953 ops/s

#===  此处显示的是单个方法的性能总览。
Result "demon.research.jmh.Sample1.wellHelloThere":
  2520334169.325 ±(99.9%) 2183136692.483 ops/s [Average]
  #=== 最小,平均,最大 
  (min, avg, max) = (1888390724.003, 2520334169.325, 3100584563.561), stdev = 566953666.190
  CI (99.9%): [337197476.842, 4703470861.808] (assumes normal distribution)


# Run complete. Total time: 00:01:41
#======  下面内容都是总结。
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
#==== 所有测试的总结
Benchmark                Mode  Cnt           Score            Error  Units
Sample1.wellHelloThere  thrpt    5  2520334169.325 ± 2183136692.483  ops/s

Throughout


# JMH version: 1.36
# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13
# VM invoker: D:\Program Files\Java\jdk1.8.0_181\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\lib\idea_rt.jar=62999:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 5 iterations, 10 s each                            #5次迭代,每次迭代10秒。
# Measurement: 5 iterations, 10 s each                       #5次迭代,每次迭代10秒。
# Timeout: 10 min per iteration                              #每次迭代的超时时间。
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: demon.research.jmh.Sample2.measureThroughput

# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1                                               #fork 1 个JVM
# Warmup Iteration   1: 9.215 ops/s                          #预热 执行了5次迭代。
# Warmup Iteration   2: 9.210 ops/s
# Warmup Iteration   3: 9.219 ops/s
# Warmup Iteration   4: 9.218 ops/s
# Warmup Iteration   5: 9.212 ops/s
Iteration   1: 9.211 ops/s                                   # 执行了5次迭代。
Iteration   2: 9.220 ops/s
Iteration   3: 9.212 ops/s
Iteration   4: 9.232 ops/s
Iteration   5: 9.217 ops/s


Result "demon.research.jmh.Sample2.measureThroughput":                #测试的哪个方法。
  9.218 ±(99.9%) 0.033 ops/s [Average]                                #平均值
  (min, avg, max) = (9.211, 9.218, 9.232), stdev = 0.008              #最小,最大,平均,标准差
 # 9.218 +0.033 = 9.251
 # 9.218 - 0.033 = 9.185
  CI (99.9%): [9.186, 9.251] (assumes normal distribution)            #置信区间CI


# Run complete. Total time: 00:01:42                                #总共用了1:42分。

REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
# 执行了5次。性能为:每秒执行9.218次。
Benchmark                   Mode  Cnt  Score   Error  Units
Sample2.measureThroughput  thrpt    5  9.218 ± 0.033  ops/s

置信区间展现的是这个参数的真实值有一定概率落在测量结果的周围的程度,其给出的是被测量参数的测量值的可信程度,即前面所要求的“一个概率”

AverageTime

Result "demon.research.jmh.Sample2.measureAvgTime":
  108820.556 ±(99.9%) 261.796 us/op [Average]
  (min, avg, max) = (108757.040, 108820.556, 108914.449), stdev = 67.988
  CI (99.9%): [108558.760, 109082.352] (assumes normal distribution)

SampleTime

Result "demon.research.jmh.Sample2.measureSamples":
  N = 464
  mean = 108618.293 ±(99.9%) 153.028 us/op

  Histogram, us/op:
    [100000.000, 101250.000) = 0  
    [101250.000, 102500.000) = 0 
    [102500.000, 103750.000) = 1              #有1次的性能数据落点在这个区间之中。
    [103750.000, 105000.000) = 0 
    [105000.000, 106250.000) = 2              #有2次的性能数据落点在这个区间之中。
    [106250.000, 107500.000) = 43 
    [107500.000, 108750.000) = 222            #有222次的性能数据落点在这个区间之中。
    [108750.000, 110000.000) = 161 
    [110000.000, 111250.000) = 33 
    [111250.000, 112500.000) = 1 
    [112500.000, 113750.000) = 0 
    [113750.000, 115000.000) = 1 
    [115000.000, 116250.000) = 0 
    [116250.000, 117500.000) = 0 
    [117500.000, 118750.000) = 0 

  Percentiles, us/op:
      p(0.0000) = 102629.376 us/op
     p(50.0000) = 108527.616 us/op                                  #第50分位置大的数据
     p(90.0000) = 109838.336 us/op                                  #第90分位置大的数据
     p(95.0000) = 110329.856 us/op
     p(99.0000) = 110932.787 us/op
     p(99.9000) = 114688.000 us/op                                  #第99.9分位置大的数据
     p(99.9900) = 114688.000 us/op
     p(99.9990) = 114688.000 us/op
     p(99.9999) = 114688.000 us/op
    p(100.0000) = 114688.000 us/op

#总共进行了464次的调用,该方法的平均响应时间为108618.293 微秒  
Benchmark                                          Mode  Cnt       Score     Error   Units
Sample2.measureSamples                           sample  464  108618.293 ± 153.028   us/op

Sample2.measureSamples:measureSamples·p0.00      sample       102629.376             us/op
Sample2.measureSamples:measureSamples·p0.50      sample       108527.616             us/op
Sample2.measureSamples:measureSamples·p0.90      sample       109838.336             us/op
Sample2.measureSamples:measureSamples·p0.95      sample       110329.856             us/op
Sample2.measureSamples:measureSamples·p0.99      sample       110932.787             us/op
Sample2.measureSamples:measureSamples·p0.999     sample       114688.000             us/op
Sample2.measureSamples:measureSamples·p0.9999    sample       114688.000             us/op
Sample2.measureSamples:measureSamples·p1.00      sample       114688.000             us/op

SingleShotTime

# JMH version: 1.36
# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13
# VM invoker: D:\Program Files\Java\jdk1.8.0_181\jre\bin\java.exe
# VM options: -javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\lib\idea_rt.jar=52600:D:\Program Files\JetBrains\IntelliJ IDEA 2018.2.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
#=======没有进行warmup
# Warmup:  
# Measurement: 1 iterations, single-shot each
# Timeout: 10 min per iteration
# Threads: 1 thread
# Benchmark mode: Single shot invocation time
# Benchmark: demon.research.jmh.Sample2.measureSingleShot

#==========就只执行了一次。
# Run progress: 100.00% complete, ETA 00:00:00
# Fork: 1 of 1
Iteration   1: 111736.300 us/op

All

# 包括所有模式。

JMH 陷阱

在使用 JMH 的过程中,一定要避免一些陷阱。比如 JIT 优化中的死码消除,比如以下代码:@Benchmark

public void testStringAdd(Blackhole blackhole) {
    String a = "";
    for (int i = 0; i < length; i++) {
        a += i;
    }

}JVM 可能会认为变量 a 从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。JMH 提供了两种方式避免这种问题,一种是将这个变量作为方法返回值 return a,一种是通过 Blackhole 的 consume 来避免 JIT 的优化消除。其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用 Fork 隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等,感兴趣的可以阅读 https://github.com/lexburner/JMH-samples 了解全部的陷阱。

}JVM 可能会认为变量 a 从来没有使用过,从而进行优化把整个方法内部代码移除掉,这就会影响测试结果。JMH 提供了两种方式避免这种问题,一种是将这个变量作为方法返回值 return a,一种是通过 Blackholeconsume 来避免 JIT 的优化消除。其他陷阱还有常量折叠与常量传播、永远不要在测试中写循环、使用 Fork 隔离多个测试方法、方法内联、伪共享与缓存行、分支预测、多线程测试等,感兴趣的可以阅读 https://github.com/lexburner/JMH-samples 了解全部的陷阱。

你可能感兴趣的:(JAVA,java,jvm,JMH)