最近在琢磨Java性能优化,感触最深的一句话是,不要轻易优化,不要盲目自信的优化,一定要基于测试对比进行优化。比如对于一个可优化的小技巧,网上说的各种天花乱坠,也有可能两篇文章说的不一样,到底信那个?信自己的测试!所以我选择了学习JMH。
JMH 是一个由 OpenJDK/Oracle 里面那群开发了 Java 编译器的大牛们所开发的 Micro Benchmark Framework 。何谓 Micro Benchmark 呢?简单地说就是在 method 层面上的 benchmark,精度可以精确到微秒级。可以看出 JMH 主要使用在当你已经找出了热点函数,而需要对热点函数进行进一步的优化时,就可以使用 JMH 对优化的效果进行定量的分析。
JMH官方examples
我用的是Idea,gradle,真的有点坑,所以写下这篇文章记录,抛砖引玉,避免别人再采坑。
我只是想run一个最简单的example...
-
可以download 我的git示例 JMH-Example
-
目录结构
目录结构 -
gradle.build
group = 'jmh-example'
version = '1.0'
apply plugin: 'java'
sourceCompatibility = 1.8
repositories {
jcenter()
mavenCentral()
}
dependencies {
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.8.1'
compile group: 'org.openjdk.jmh', name: 'jmh-core', version: '1.21'
compile group: 'org.openjdk.jmh', name: 'jmh-generator-annprocess', version: '1.21'
}
sourceSets {
main {
java.srcDirs = ['src']
}
test {
java.srcDirs = ['test']
}
}
- HelloBenchmark.java
package main.java;
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;
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@State(Scope.Thread) //Thread: 该状态为每个线程独享。
public class HelloBenchmark {
@Benchmark
public int sleepAWhile() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// ignore
}
return 0;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(HelloBenchmark.class.getSimpleName()) //benchmark 所在的类的名字,注意这里是使用正则表达式对所有类进行匹配的
.forks(1) //进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个进程来进行测试
.warmupIterations(5) //预热的迭代次数
.measurementIterations(5) //实际测量的迭代次数
.build();
new Runner(opt).run();
}
}
- 坑
Unable to find the resource: /META-INF/BenchmarkList
Error:(3, 0) Unable to load class 'org.gradle.api.provider.Property'.
Possible causes for this unexpected error include:<ul><li>Gradle's dependency cache may be corrupt (this sometimes occurs after a network connection timeout.)
<a href="syncProject">Re-download dependencies and sync project (requires network)</a></li><li>The state of a Gradle build process (daemon) may be corrupt. Stopping all Gradle daemons may solve this problem.
<a href="stopGradleDaemons">Stop Gradle build processes (requires restart)</a></li><li>Your project may be using a third-party plugin which is not compatible with the other plugins in the project or the version of Gradle requested by the project.</li></ul>In the case of corrupt Gradle processes, you can also try closing the IDE and then killing all Java processes.
如果你遇到了上述错误,看这篇文章就对了。其实根本不必安装任何插件。想要run起来只需要包含下面两个库就可以了。
jmh-core
jmh-generator-annprocess
如果找不到/META-INF/BenchmarkList,原因是没有导入jmh-generator-annprocess。可以直接手动下载jmh-generator-annprocess就可以run起来了。
image.png
image.png
- final solution
真正的原因在于如果是以testCompile开头引入依赖的话,而运行的代码在main目录下而不是test目录,会造成main模块不会依赖jmh-generator-annprocess。这种情况下可以把testCompile改为compile即可。
#把testCompile改为compile即可
testCompile group: 'org.openjdk.jmh', name: 'jmh-generator-annprocess', version: '1.21'
-
testCompile
testCompile -
compile
compile -
run output
/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/bin/java -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/lib/tools.jar:/Users/liuyihao/Documents/GitHub/JMH/out/production/classes:/Users/liuyihao/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-lang3/3.8.1/6505a72a097d9270f7a9e7bf42c4238283247755/commons-lang3-3.8.1.jar:/Users/liuyihao/.gradle/caches/modules-2/files-2.1/org.openjdk.jmh/jmh-core/1.21/442447101f63074c61063858033fbfde8a076873/jmh-core-1.21.jar:/Users/liuyihao/.gradle/caches/modules-2/files-2.1/org.openjdk.jmh/jmh-generator-annprocess/1.21/7aac374614a8a76cad16b91f1a4419d31a7dcda3/jmh-generator-annprocess-1.21.jar:/Users/liuyihao/.gradle/caches/modules-2/files-2.1/net.sf.jopt-simple/jopt-simple/4.6/306816fb57cf94f108a43c95731b08934dcae15c/jopt-simple-4.6.jar:/Users/liuyihao/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.2/ec2544ab27e110d2d431bdad7d538ed509b21e62/commons-math3-3.2.jar main.java.HelloBenchmark
# JMH version: 1.21
# VM version: JDK 1.8.0_151, Java HotSpot(TM) 64-Bit Server VM, 25.151-b12
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/bin/java
# VM options: -Dfile.encoding=UTF-8
# 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: Average time, time/op
# Benchmark: main.java.HelloBenchmark.sleepAWhile
# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration 1: 503670.436 us/op
# Warmup Iteration 2: 503833.837 us/op
# Warmup Iteration 3: 503956.040 us/op
# Warmup Iteration 4: 503632.164 us/op
# Warmup Iteration 5: 503362.804 us/op
Iteration 1: 503255.077 us/op
Iteration 2: 504072.009 us/op
Iteration 3: 503357.188 us/op
Iteration 4: 503521.192 us/op
Iteration 5: 503713.174 us/op
Result "main.java.HelloBenchmark.sleepAWhile":
503583.728 ±(99.9%) 1245.398 us/op [Average]
(min, avg, max) = (503255.077, 503583.728, 504072.009), stdev = 323.426
CI (99.9%): [502338.330, 504829.126] (assumes normal distribution)
# Run complete. Total time: 00:01: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.
Benchmark Mode Cnt Score Error Units
HelloBenchmark.sleepAWhile avgt 5 503583.728 ± 1245.398 us/op
Process finished with exit code 0
或者把源文件统一放在test目录下。
-
重构。若重构后测试类名或者包名发生变化,需要删除编译目录重新编译。主要是删除BenchmarkList文件
image.png
网友评论