什么是Benchmark?
Benchmark是一個評價方式耐齐,在整個計算機領(lǐng)域有著長期的應(yīng)用笤成。Benchmark在計算機領(lǐng)域應(yīng)用最成功的就是性能測試,主要測試負(fù)載的執(zhí)行時間漓帅、傳輸速度锨亏、吞吐量痴怨、資源占用率等。像Redis就有自己的基準(zhǔn)測試工具redis-benchmark器予。
什么是JMH?
JMH (the Java Microbenchmark Harness) ,它被作為Java9的一部分來發(fā)布浪藻,但是我們完全不需要等待Java9,而可以方便的使用它來簡化我們測試乾翔,它能夠照看好JVM的預(yù)熱爱葵、代碼優(yōu)化,讓你的測試過程變得更加簡單反浓。
由于jvm底層不斷的升級,隨著代碼執(zhí)行次數(shù)的增多,jvm會不斷的進(jìn)行編譯優(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
在測試中,隨機進(jìn)行采樣執(zhí)行的時間 - SingleShotTime
在每次執(zhí)行中計算耗時 - All
所有模式巧婶,這個在內(nèi)部測試中常用
State
- Benchmark
同一個benchmark在多個線程之間共享實例 - Group
同一個線程在同一個group里共享實例乾颁。group定義參考注解 @Group - Thread
不同線程之間的實例不共享
啟動基準(zhǔn)測試:
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í)行基準(zhǔn)測試的目標(biāo),可以傳入正則。
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輪測試,為了達(dá)到更加準(zhǔn)確的效果,可以適當(dāng)增大該值湿右。
輸出基準(zhǔn)測試結(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:基準(zhǔn)測試名稱
Mode:基礎(chǔ)測試模式(這里指吞吐量)
Cnt:次數(shù)
Score:這里指每秒執(zhí)行的次數(shù),當(dāng)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的性能是遠(yuǎn)高于BeanUtils的。
更深一層-避免JIT優(yōu)化
我們在測試的時候丈莺,一定要避免JIT優(yōu)化划煮。對于有一些代碼,編譯器可以推導(dǎo)出一些計算是多余的缔俄,并且完全消除它們弛秋。 如果我們的基準(zhǔn)測試?yán)镉胁糠执a被清除了,那測試的結(jié)果就不準(zhǔn)確了:
@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/】