什么是Benchmark?
Benchmark是一個評價方式,在整個計算機領(lǐng)域有著長期的應(yīng)用。Benchmark在計算機領(lǐng)域應(yīng)用最成功的就是性能測試,主要測試負載的執(zhí)行時間、傳輸速度、吞吐量、資源占用率等。像Redis就有自己的基準測試工具redis-benchmark。
什么是JMH?
JMH (the Java Microbenchmark Harness) ,它被作為Java9的一部分來發(fā)布,但是我們完全不需要等待Java9,而可以方便的使用它來簡化我們測試,它能夠照看好JVM的預(yù)熱、代碼優(yōu)化,讓你的測試過程變得更加簡單。
由于jvm底層不斷的升級,隨著代碼執(zhí)行次數(shù)的增多,jvm會不斷的進行編譯優(yōu)化,導(dǎo)致要執(zhí)行很多次才能得出穩(wěn)定的數(shù)據(jù).故我們需要頻繁的編寫"預(yù)熱"代碼,然后還需要不厭其煩的打印出測試結(jié)果。幸好,我們有JMH!使我們的基礎(chǔ)測試變得很簡單了。
導(dǎo)入JMH依賴
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.21</version>
</dependency>
創(chuàng)建Demo:
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class Helloworld {
@Benchmark
public void firstBenchmark() {
}
}
BenchmarkMode
- Throughput
每段時間執(zhí)行的次數(shù),一般是秒 - AverageTime
平均時間,每次操作的平均耗時 - SampleTime
在測試中,隨機進行采樣執(zhí)行的時間 - SingleShotTime
在每次執(zhí)行中計算耗時 - All
所有模式,這個在內(nèi)部測試中常用
State
- Benchmark
同一個benchmark在多個線程之間共享實例 - Group
同一個線程在同一個group里共享實例。group定義參考注解 @Group - Thread
不同線程之間的實例不共享
啟動基準測試:
public class HelloworldRunner {
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include("Helloworld")
.warmupIterations(10)
.warmupTime(TimeValue.seconds(1))
.measurementIterations(10)
.measurementTime(TimeValue.seconds(1))
.forks(1)
.build();
new Runner(opt).run();
}
}
include("Helloworld")
是指定要執(zhí)行基準測試的目標,可以傳入正則。
warmupIterations(10)
在執(zhí)行前預(yù)熱10次。
warmupTime(TimeValue.seconds(1))
每一次預(yù)熱1秒.
measurementIterations(10)
重復(fù)執(zhí)行10次,
measurementTime(TimeValue.seconds(1))
每一次執(zhí)行1秒。
forks(1)
指的只做1輪測試,為了達到更加準確的效果,可以適當增大該值。
輸出基準測試結(jié)果:
Result "com.pingan.jmh.HelloWorld.firstBenchmark":
2703833258.555 ±(99.9%) 354675008.250 ops/s [Average]
(min, avg, max) = (2157247993.082, 2703833258.555, 2894733254.695), stdev = 234595557.867
CI (99.9%): [2349158250.305, 3058508266.805] (assumes normal distribution)
# Run complete. Total time: 00:00:21
Benchmark Mode Cnt Score Error Units
HelloWorld.firstBenchmark thrpt 10 2703833258.555 ± 354675008.250 ops/s
Benchmark:基準測試名稱
Mode:基礎(chǔ)測試模式(這里指吞吐量)
Cnt:次數(shù)
Score:這里指每秒執(zhí)行的次數(shù),當Mode改變的時候,Score含義不同。
Units:單位,這里指每秒執(zhí)行的次數(shù)
實戰(zhàn)
我們都知道Apache的BeanUtils.copyProperties()表現(xiàn)不是很好,所以這里我們使用jmh測試一下和PropertyUtils的差異。
直接上代碼:
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class BeanCopyPropsBenchMark {
private User user;
private Person person;
@Setup
public void init(){
user = new User(3,"jerrik",27,"深圳");
person = new Person();
}
@Benchmark
public Person runBeanUtils() {
try {
BeanUtils.copyProperties(user,person);
return person;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Benchmark
public Person runPropertyUtils() {
try {
PropertyUtils.copyProperties(person,user);
return person;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(BeanCopyPropsBenchMark.class.getSimpleName())
.forks(1)
.threads(1)
.measurementIterations(10)
.measurementTime(TimeValue.seconds(1))
.warmupIterations(10)
.warmupTime(TimeValue.seconds(1))
.build();
new Runner(options).run();
}
}
結(jié)果:
Result "com.pingan.jmh.BeanCopyPropsBenchMark.runPropertyUtils":
401720.952 ±(99.9%) 41189.586 ops/s [Average]
(min, avg, max) = (325423.974, 401720.952, 417070.687), stdev = 27244.361
CI (99.9%): [360531.367, 442910.538] (assumes normal distribution)
# Run complete. Total time: 00:00:42
Benchmark Mode Cnt Score Error Units
BeanCopyPropsBenchMark.runBeanUtils thrpt 10 55708.695 ± 9226.542 ops/s
BeanCopyPropsBenchMark.runPropertyUtils thrpt 10 401720.952 ± 41189.586 ops/s
PropertyUtils一秒內(nèi)的執(zhí)行次數(shù)為401720.952 ± 41189.586,而BeanUtils的才55708.695 ± 9226.542,執(zhí)行效率相差快8倍。可以看出PropertyUtils的性能是遠高于BeanUtils的。
更深一層-避免JIT優(yōu)化
我們在測試的時候,一定要避免JIT優(yōu)化。對于有一些代碼,編譯器可以推導(dǎo)出一些計算是多余的,并且完全消除它們。 如果我們的基準測試里有部分代碼被清除了,那測試的結(jié)果就不準確了:
@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class BenchmarkWithoutJit {
double x = Math.PI;
@Benchmark
public void withJIT(){
Math.log(x);
}
@Benchmark
public void withoutJIT(Blackhole blackhole){
blackhole.consume(Math.log(x));//consume()可以避免JIT的優(yōu)化
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include("BenchmarkWithoutJit")
.warmupIterations(10)
.warmupTime(TimeValue.seconds(1))
.measurementIterations(10)
.measurementTime(TimeValue.seconds(1))
.forks(1)
.build();
new Runner(opt).run();
}
}
//output
Benchmark Mode Cnt Score Error Units
BenchmarkWithoutJit.withJIT thrpt 10 2509923125.204 ± 430839988.021 ops/s
BenchmarkWithoutJit.withoutJIT thrpt 10 36900419.444 ± 778621.522 ops/s
可知使用JIT優(yōu)化的情況下,性能要高出很多倍。
根據(jù)JMH聯(lián)想到的應(yīng)用場景
比如各種序列化機制的速率對比,cas和加鎖的性能對比,反射和getter,setter的性能對比,集合框架的性能對比等等。
其他具體更多高級用法可以參考jmh官網(wǎng)的Demo【http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/】