大家好,又见面了,我是你们的朋友全栈君。
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.32</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.32</version>
</dependency>
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 Example_01_HelloJMH {
@Benchmark
public String sayHello() {
return "HELLO JMH!";
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(Example_01_HelloJMH.class.getSimpleName())
.forks(1)
.build();
new Runner(options).run();
}
}
# JMH version: 1.32
# VM version: JDK 1.8.0_241, Java HotSpot(TM) 64-Bit Server VM, 25.241-b07
# VM invoker: G:\Java\jdk1.8.0_241\jre\bin\java.exe
# VM options: -javaagent:G:\JetBrains\IntelliJ IDEA 2020.2.3\lib\idea_rt.jar=56180:G:\JetBrains\IntelliJ IDEA 2020.2.3\bin -Dfile.encoding=UTF-8
# Blackhole mode: full + dont-inline hint
# 预热配置
# 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: com.ziroom.test.Example_01_HelloJMH.sayHello
# 运行过程的输出
# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration 1: 2924740803.993 ops/s
# Warmup Iteration 2: 2916472711.387 ops/s
# Warmup Iteration 3: 3024204715.897 ops/s
# Warmup Iteration 4: 3051723946.668 ops/s
# Warmup Iteration 5: 2924014544.301 ops/s
Iteration 1: 2909665054.710 ops/s
Iteration 2: 2989675862.826 ops/s
Iteration 3: 2965046292.629 ops/s
Iteration 4: 3020263765.220 ops/s
Iteration 5: 2929485177.735 ops/s
# 当前方法测试结束的报告
Result "com.ziroom.test.Example_01_HelloJMH.sayHello":
2962827230.624 ±(99.9%) 171803440.922 ops/s [Average]
(min, avg, max) = (2909665054.710, 2962827230.624, 3020263765.220), stdev = 44616808.022
CI (99.9%): [2791023789.702, 3134630671.547] (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跑完后的最终报告
Benchmark Mode Cnt Score Error Units
Example_01_HelloJMH.sayHello thrpt 5 2962827230.624 ± 171803440.922 ops/s
import com.spadesk.springbootjmhtest.controller.TestController;
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.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class SpringBootBenchMark {
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(SpringBootBenchMark.class.getSimpleName())
.warmupIterations(3)
.measurementIterations(3)
.forks(3)
.build();
new Runner(options).run();
}
private ConfigurableApplicationContext springContext;
private TestController testController;
@Setup
public void setUp() {
// SpringbootJmhTestApplication.class是项目里的spring boot启动类
springContext = SpringApplication.run(SpringbootJmhTestApplication.class);
// 加载 Bean,此时会自动注入 AService 和 BService
testController = springContext.getBean(TestController.class);
}
@TearDown
public void tearDown() {
springContext.close();
}
@Benchmark
public void testStringBuffer() {
// controller 调用 AService 的方法
testController.testAService();
}
@Benchmark
public void testStringBuilder() {
// controller 调用 BService 的方法
testController.testBService();
}
}
org.openjdk.jmh.runner.Runner
类去运行org.openjdk.jmh.runner.Runner
即可。
OptionsBuilder
对象去构建。这个Builder对象是流式的。
@benchmark
所在的类的名字,这里可以使用正则表达式对所有类进行匹配
include
相反的
类型 | 描述 | 备注 |
---|---|---|
NANOSECONDS | 纳秒 | – |
MICROSECONDS | 微秒 | 1微秒=1000纳秒 |
MILLISECONDS | 毫秒 | 1毫秒=100微秒 |
SECONDS | 秒 | 1秒=1000毫秒 |
MINUTES | 分钟 | 1分钟=60秒 |
HOURS | 小时 | 1小时=60分钟 |
DAYS | 天 | 1天=24小时 |
Benchmark
对基准有效负载进行了划分,JMH专门将其视为包含基准代码的包装器。为了可靠地运行基准测试,JMH为这些包装器方法强制执行一些严格的属性,包括但不限于:
public
State
注解的类(JMH将在调用该方法时进行注入)或JMH基础设施类(org.openjdk.jmh.infra.Control
,org.openjdk.jmh.infra.Blackhole
)
State
放置在封闭类上时进行同步。
Benchmark
方法中调用它们。
Mode
类型,默认Mode.Throughput
Mode
类型数组,默认Mode.Throughput
Mode
枚举类枚举了JMH的测试模式,分别为
模式 | 介绍 | 单位 |
---|---|---|
Throughout | 整体吞吐量,例如“1秒内可以执行多少次调用” | op/time |
AverageTime | 平均时间,执行程序的平均耗时,例如“每次调用平均耗时xxx毫秒” | time/op |
SampleTime | 执行时间随机取样,输出执行时间的结果分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内” | time/op |
SingleShotTime | 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能 | time/op |
All | 全部模式 | – |
单位中的
op
表明的是一次操作,默认一次操作指的是执行一次测试方法。可是咱们能够指定调用多少次测试方法算做一次操作。在 JMH 中称做操作中的批处理次数,例如咱们能够设置执行五次测试方法算做一次操作。
@WarmUp
Options
中单独指定,优先级是:类 < 方法 < Options
。
参数 | 描述 | Options中方法 | 备注 |
---|---|---|---|
iterations | 测量次数,默认是5次 | warmupIterations(int value) | |
batchSize | 每次操作的批处理次数,默认是1,即执行一次操作调用一次预热方法 | warmupBatchSize(int value) | |
time | 单次预热持续时间,默认是10 | warmupTime(TimeValue value) | |
timeUnit | 时间单位,指定time单位,默认是秒 | – |
相关方法
WarmupMode
WarmupMode
枚举类枚举了预热模式,分别为
参数 描述 备注 INDI 对每个基准进行单独的热身 BULK 在任何基准测试开始之前进行批量预热。 BULK_INDI 在任何基准测试开始之前进行批量预热,然后对每个基准测试进行单独预热
避免 JIT 优化
@WarmUp
对代码进行预热,防止前后执行的时间差别。但这仅仅只是解决时间前后存在的问题,JIT 还存在着其余很是多的优化手段,这是 JVM 牛逼的地方,但倒是 JMH 难受的地方。
@Measurement
Options
中单独指定,优先级是:类 < 方法 < Options
。
参数 | 描述 | Options中的方法 | 备注 |
---|---|---|---|
iterations | 测量次数,默认是5次 | measurementIterations(int var1) | |
batchSize | 每次操作的批处理次数,默认是1,即执行一次操作调用一次测试方法 | measurementBatchSize(int var1) | |
time | 单次测量持续时间,默认是10 | measurementTime(TimeValue var1) | |
timeUnit | 时间单位,指定time单位,默认是秒 | – |
参数 | 描述 | 默认值 | 备注 |
---|---|---|---|
int value() | 测试时fork的数,0表示不进行fork | BLANK_FORKS | int BLANK_FORKS = -1; |
int warmups() | 预热时的fork数 | BLANK_FORKS | String BLANK_ARGS = “blank_blank_blank_2014”; |
String jvm() | 要运行的JVM | BLANK_ARGS | Options中的jvm作用一样 |
String[] jvmArgs() | 要在命令行中替换的JVM参数 | { BLANK_ARGS } | Options中的jvmArgs作用一样,参数为String可变长参数 |
String[] jvmArgsPrepend() | 要在命令行中添加的JVM参数 | { BLANK_ARGS } | Options中的jvmArgsPrepend作用一样,参数为String可变长参数 |
String[] jvmArgsAppend() | 要在命令行中追加的JVM参数 | { BLANK_ARGS } | Options中的jvmArgsAppend作用一样,参数为String可变长参数 |
JVM因为使用了profile-guided optimization而“臭名昭著”,这对于微基准测试来说十分不友好,因为不同测试方法的profile混杂在一起,“互相伤害”彼此的测试结果。对于每个
Benchmark
方法使用一个独立的进程可以解决这个问题,这也是JMH的默认选项。注意不要设置为0,设置为n则会启动n个进程执行测试(似乎也没有太大意义)。 fork选项也可以通过方法注解以及启动参数来设置
int
int
,默认10
TimeUnit
,默认SECONDS
(秒)
TimeUnit
Scope
Scope
包含如下:
类型 描述 备注 Thread 默认的State,每个测试线程分配一个实例; Benchmark 所有测试线程共享一个实例,用于测试有状态实例在多线程共享下的性能; Group 每个线程组共享一个实例;
Level
Level
参数表明粒度,粒度从粗到细分别是
类型 描述 备注 Trial 默认级别。每个Benchmark方法前后 Iteration 每个Benchmark方法每次迭代前后 Invocation 每个Benchmark方法每次调用前后,谨慎使用;
State
生命周期,Setup
只能在State
类中使用。
Setup
方法将由一个有权访问State
的线程执行,并且没有确切定义哪个线程。
State
在线程之间共享,那意味着TearDown
可能由不同的线程执行。
State
Level
,和@Setup
一样
State
生命周期,TearDown
只能在State
类中使用。
TearDown
方法将由一个有权访问State
的线程执行,并且没有确切定义哪个线程。
State
在线程之间共享,那意味着Setup
可能由不同的线程执行。
@State
注解
String
数组,默认值{ BLANK_ARGS }
,String BLANK_ARGS = "blank_blank_blank_2014";
String
可变长参数
final
字段,只能定义在State
类中.
Setup
方法之前,JMH将把值注入带注释的字段中。
State
的任何构造方法中都可以访问。
Benchmark
方法分组
String
,默认值为group。
Benchmark
方法以生成非对称基准测试。
Group
注解的Benchmark
方法。
Benchmark
方法的线程数默认为单个线程,可以在GroupThreads
设置线程数。
Group
中有两个Benchmark
方法,每个方法都有GroupThreads(4)
,将运行8*N个线程,其中N是一个整数。
Benchmark
方法的运行。
int
,默认1个线程
@Benchmark @OperationsPerInvocation(10) public void test() { for (int i = 0; i <= 10; i++) { // do something } } // 调用一次 test() 方法,相当于10次操作。
Benchmark
方法调用的操作数,类型int
,默认为1
test1:test1·p0.99 sample ≈ 10⁻⁶ s/op test1:test1·p0.999 sample ≈ 10⁻⁶ s/op test2:test2·p0.99 sample ≈ 10⁻⁷ s/op test2:test2·p0.999 sample ≈ 10⁻⁷ s/op
@OperationsPerInvocation(1)
)。你可以手动调整一个方法代表几个操作,如果把操作数设置为10,相同的方法,在输出结果中,吞吐量会提高10倍左右,响应时间快10倍左右。
Mode
Mode
枚举分别为
类型 描述 备注 BREAK 将断点插入生成的编译代码。 PRINT 打印该方法及其配置文件。 EXCLUDE 从编译中排除该方法。 INLINE 强制内联。 DONT_INLINE 强制跳过内联。 COMPILE_ONLY 仅编译此方法,而不编译其他方法。
State
对象标记为辅助次要结果的承载器。使用此注释标记类将使JMH将其公共字段和返回结果的公共方法作为次要基准度量的基础。
Type
Type
枚举分别为
类型 描述 备注 OPERATIONS 统计“操作”,这与执行Benchmark
方法的次数相关。如果该计数器在每次Benchmark
方法调用时递增,那么它将生成一个类似于主基准结果的度量。该计数器将由线束标准化为基准时间。 EVENTS 统计“事件”,即工作负载生命周期中的一次性事件。此计数器将无法标准化为时间。
BenchmarkMode
),因为并非每个模式都计算时间或操作。 Mode.AverageTime
和Mode.Throughput
,只支持吞吐量。
Scope.Thread
状态对象。将其与其他状态一起使用时会编译错误。这意味着计数器本质上是本地的线程。
public
。
boolean/Boolean
和char/Character
。使用类型不兼容的公共字段/方法会编译错误。
void
返回类型的方法不必进行类型检查。这意味着Setup
和TearDown
方法和AuxCounters
一起使用比较好。
AuxCounters
实例中的公共字段将在开始迭代之前重置,并在迭代结束时读取。这允许基准代码避免对这些对象进行复杂的生命周期处理。
AuxCounters
类存在歧义,JMH将无法编译基准测试。
int
的可变长参数
boolean
,默认为true
boolean
,默认为false
org.openjdk.jmh.runner.options.VerboseMode
,该类型分别为
类型 描述 备注 SILENT 保持完全沉默(不输出) NORMAL 正常输出 默认值 EXTRA 输出额外信息
boolean
,默认为false
String
,默认值为jmh-result
org.openjdk.jmh.results.format.ResultFormatType
,该类型分别为一下五种类型:TEXT、CSV、SCSV、JSON和LATEX。默认为CSV
String
org.openjdk.jmh.runner.options.Options
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/157459.html原文链接:https://javaforall.cn