JMH性能測試


  JMH,即Java Microbenchmark Harness,是專門用於代碼微基准測試的工具套件。何謂Micro Benchmark呢?簡單的來說就是基於方法層面的基准測試,精度可以達到微秒級。當你定位到熱點方法,希望進一步優化方法性能的時候,就可以使用JMH對優化的結果進行量化的分析。

  JMH比較典型的應用場景有:

  • 想准確的知道某個方法需要執行多長時間,以及執行時間和輸入之間的相關性;
  • 對比接口不同實現在給定條件下的吞吐量,找到最優實現
  • 查看多少百分比的請求在多長時間內完成

  1、JMH環境搭建

 1     <dependency>
 2         <groupId>org.openjdk.jmh</groupId>
 3         <artifactId>jmh-core</artifactId>
 4         <version>0.7.1</version>
 5     </dependency>
 6     <dependency>
 7         <groupId>org.openjdk.jmh</groupId>
 8         <artifactId>jmh-generator-annprocess</artifactId>
 9         <version>0.7.1</version>
10         <scope>provided</scope>
11     </dependency>
12 
13     <plugin>
14         <groupId>org.apache.maven.plugins</groupId>
15         <artifactId>maven-shade-plugin</artifactId>
16         <executions>
17             <execution>
18                 <phase>package</phase>
19                 <goals>
20                     <goal>shade</goal>
21                 </goals>
22                 <configuration>
23                     <finalName>microbenchmarks</finalName>
24                     <transformers>
25                         <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
26                             <mainClass>org.openjdk.jmh.Main</mainClass>
27                         </transformer>
28                     </transformers>
29                 </configuration>
30             </execution>
31         </executions>
32     </plugin>

  2、用例(此用例用於對比Calendar和dateTime在高並發情況下的性能情況):

 1 package jmh;
 2 import java.util.Calendar;
 3 import java.util.concurrent.TimeUnit;
 4 
 5 import org.joda.time.DateTime;
 6 import org.openjdk.jmh.annotations.Benchmark;
 7 import org.openjdk.jmh.annotations.BenchmarkMode;
 8 import org.openjdk.jmh.annotations.Mode;
 9 import org.openjdk.jmh.annotations.OutputTimeUnit;
10 import org.openjdk.jmh.annotations.Threads;
11 import org.openjdk.jmh.runner.Runner;
12 import org.openjdk.jmh.runner.options.Options;
13 import org.openjdk.jmh.runner.options.OptionsBuilder;
14 
15 @OutputTimeUnit(TimeUnit.NANOSECONDS)
16 @BenchmarkMode(Mode.AverageTime)
17 public class JMHTest {
18 
19     static int millis = 24 * 3600 * 1000;
20 
21     public static void main(String[] args) throws Exception {
22         Options options = new OptionsBuilder().include(JMHTest.class.getName()).forks(1).build();
23         new Runner(options).run();
24     }
25 
26     @Benchmark
27     @Threads(5)
28     public void runCalendar() {
29         Calendar calendar = Calendar.getInstance();
30     }
31 
32     @Benchmark
33     @Threads(5)
34     public void runJoda() {
35         DateTime dateTime = new DateTime();
36     }
37 
38     //
39     @Benchmark
40     @Threads(5)
41     public void runSystem() {
42         long result = System.currentTimeMillis() / millis;
43     }
44 }

  3、執行

  可能會遇到報錯:

Exception in thread "main" java.lang.RuntimeException: ERROR: Unable to find the resource: /META-INF/BenchmarkList 

  解決方法:

    在eclipse中安裝m2e-apt插件,然后啟用Automatically configure JDT APT選項;

  

  

  運行如果報錯如下,是因為jmh不支持debug模式運行

 

# JMH version: 1.21
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:55262 -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.lz.apply.soa.JMHTest.runCalendar

# Run progress: 0.00% complete, ETA 00:05:00
# Fork: 1 of 1
FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
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]
<forked VM failed with exit code 1>
<stdout last='20 lines'>
FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
</stdout>
<stderr last='20 lines'>
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]
</stderr>

# JMH version: 1.21
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:55262 -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.lz.apply.soa.JMHTest.runJoda

# Run progress: 0.00% complete, ETA 00:05:00
# Fork: 1 of 1
FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
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]
<forked VM failed with exit code 1>
<stdout last='20 lines'>
FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
</stdout>
<stderr last='20 lines'>
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]
</stderr>

# JMH version: 1.21
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:55262 -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.lz.apply.soa.JMHTest.runSystem

# Run progress: 0.00% complete, ETA 00:05:00
# Fork: 1 of 1
FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
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]
<forked VM failed with exit code 1>
<stdout last='20 lines'>
FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
</stdout>
<stderr last='20 lines'>
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]
</stderr>

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

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
ERROR: JDWP Unable to get JNI 1.2 environment, jvm->GetEnv() return code = -2
JDWP exit error AGENT_ERROR_NO_JNI_ENV(183):  [util.c:840]

  改為run運行之后運行結果:

# JMH version: 1.21
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.lz.apply.soa.JMHTest.runCalendar

# Run progress: 0.00% complete, ETA 00:05:00
# Fork: 1 of 1
# Warmup Iteration   1: 625.707 ±(99.9%) 24.593 ns/op
# Warmup Iteration   2: 623.766 ±(99.9%) 41.928 ns/op
# Warmup Iteration   3: 639.438 ±(99.9%) 8.543 ns/op
# Warmup Iteration   4: 651.421 ±(99.9%) 25.146 ns/op
# Warmup Iteration   5: 638.751 ±(99.9%) 26.477 ns/op
Iteration   1: 631.098 ±(99.9%) 14.580 ns/op
Iteration   2: 603.567 ±(99.9%) 25.887 ns/op
Iteration   3: 609.097 ±(99.9%) 24.528 ns/op
Iteration   4: 638.006 ±(99.9%) 20.327 ns/op
Iteration   5: 614.077 ±(99.9%) 33.736 ns/op


Result "org.lz.apply.soa.JMHTest.runCalendar":
  619.169 ±(99.9%) 56.722 ns/op [Average]
  (min, avg, max) = (603.567, 619.169, 638.006), stdev = 14.731
  CI (99.9%): [562.446, 675.891] (assumes normal distribution)


# JMH version: 1.21
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.lz.apply.soa.JMHTest.runJoda

# Run progress: 33.33% complete, ETA 00:03:22
# Fork: 1 of 1
# Warmup Iteration   1: 38.363 ±(99.9%) 6.992 ns/op
# Warmup Iteration   2: 34.713 ±(99.9%) 2.364 ns/op
# Warmup Iteration   3: 35.779 ±(99.9%) 2.657 ns/op
# Warmup Iteration   4: 36.549 ±(99.9%) 2.201 ns/op
# Warmup Iteration   5: 34.805 ±(99.9%) 3.346 ns/op
Iteration   1: 33.694 ±(99.9%) 2.354 ns/op
Iteration   2: 34.348 ±(99.9%) 2.880 ns/op
Iteration   3: 34.194 ±(99.9%) 3.279 ns/op
Iteration   4: 35.187 ±(99.9%) 4.125 ns/op
Iteration   5: 35.056 ±(99.9%) 3.453 ns/op


Result "org.lz.apply.soa.JMHTest.runJoda":
  34.496 ±(99.9%) 2.395 ns/op [Average]
  (min, avg, max) = (33.694, 34.496, 35.187), stdev = 0.622
  CI (99.9%): [32.101, 36.890] (assumes normal distribution)


# JMH version: 1.21
# VM version: JDK 1.8.0_91, Java HotSpot(TM) 64-Bit Server VM, 25.91-b15
# VM invoker: C:\Program Files\Java\jdk1.8.0_91\jre\bin\java.exe
# VM options: -Dfile.encoding=UTF-8
# Warmup: 5 iterations, 10 s each
# Measurement: 5 iterations, 10 s each
# Timeout: 10 min per iteration
# Threads: 5 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: org.lz.apply.soa.JMHTest.runSystem

# Run progress: 66.67% complete, ETA 00:01:41
# Fork: 1 of 1
# Warmup Iteration   1: 8.881 ±(99.9%) 1.253 ns/op
# Warmup Iteration   2: 8.773 ±(99.9%) 1.206 ns/op
# Warmup Iteration   3: 9.506 ±(99.9%) 0.462 ns/op
# Warmup Iteration   4: 9.496 ±(99.9%) 0.490 ns/op
# Warmup Iteration   5: 9.122 ±(99.9%) 0.220 ns/op
Iteration   1: 9.085 ±(99.9%) 0.172 ns/op
Iteration   2: 9.341 ±(99.9%) 0.326 ns/op
Iteration   3: 9.254 ±(99.9%) 0.293 ns/op
Iteration   4: 9.021 ±(99.9%) 0.164 ns/op
Iteration   5: 9.294 ±(99.9%) 0.359 ns/op


Result "org.lz.apply.soa.JMHTest.runSystem":
  9.199 ±(99.9%) 0.534 ns/op [Average]
  (min, avg, max) = (9.021, 9.199, 9.341), stdev = 0.139
  CI (99.9%): [8.665, 9.733] (assumes normal distribution)


# Run complete. Total time: 00:05:03

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
JMHTest.runCalendar  avgt    5  619.169 ± 56.722  ns/op
JMHTest.runJoda      avgt    5   34.496 ±  2.395  ns/op
JMHTest.runSystem    avgt    5    9.199 ±  0.534  ns/op

  測試結果表明:

    runCalendar方法平均運行時間為:619微秒

    runJoda方法的平均運行時間為:34微秒

  據用例測試結果可以看到,Calendar(非線程安全)在高並發情況下性能遠遠比不上dateTime(線程安全)。對代碼性能和安全性要求比較高的可以放棄使用Calendar和SimpleDateFormat(底層實現是Calendar),轉而使用joda-time

  4、基本概念

  Mode 

  Mode 表示 JMH 進行 Benchmark 時所使用的模式。通常是測量的維度不同,或是測量的方式不同。目前 JMH 共有四種模式:

    • Throughput: 整體吞吐量,例如“1秒內可以執行多少次調用”。
    • AverageTime: 調用的平均時間,例如“每次調用平均耗時xxx毫秒”。
    • SampleTime: 隨機取樣,最后輸出取樣結果的分布,例如“99%的調用在xxx毫秒以內,99.99%的調用在xxx毫秒以內”
    • SingleShotTime: 以上模式都是默認一次 iteration 是 1s,唯有 SingleShotTime 是只運行一次。往往同時把 warmup 次數設為0,用於測試冷啟動時的性能。

  Iteration 

  Iteration 是 JMH 進行測試的最小單位。在大部分模式下,一次 iteration 代表的是一秒,JMH 會在這一秒內不斷調用需要 benchmark 的方法,然后根據模式對其采樣,計算吞吐量,計算平均執行時間等。

  Warmup

  Warmup 是指在實際進行 benchmark 前先進行預熱的行為。為什么需要預熱?因為 JVM 的 JIT 機制的存在,如果某個函數被調用多次之后,JVM 會嘗試將其編譯成為機器碼從而提高執行速度。為了讓 benchmark 的結果更加接近真實情況就需要進行預熱。

 

  5、注解與選項

  @BenchmarkMode 

    對應Mode選項,可用於類或者方法上, 需要注意的是,這個注解的value是一個數組,可以把幾種Mode集合在一起執行,還可以設置為Mode.All,即全部執行一遍。

  @State 

    類注解,JMH測試類必須使用@State注解,State定義了一個類實例的生命周期,可以類比Spring Bean的Scope。由於JMH允許多線程同時執行測試,不同的選項含義如下:

    • Scope.Thread:默認的State,每個測試線程分配一個實例;
    • Scope.Benchmark:所有測試線程共享一個實例,用於測試有狀態實例在多線程共享下的性能;
    • Scope.Group:每個線程組共享一個實例;

  @OutputTimeUnit 

    benchmark 結果所使用的時間單位,可用於類或者方法注解,使用java.util.concurrent.TimeUnit中的標准時間單位。

  @Benchmark 

    方法注解,表示該方法是需要進行 benchmark 的對象。

  @Setup 

    方法注解,會在執行 benchmark 之前被執行,正如其名,主要用於初始化。

  @TearDown 

    方法注解,與@Setup 相對的,會在所有 benchmark 執行結束以后執行,主要用於資源的回收等。

  @Param 

    成員注解,可以用來指定某項參數的多種情況。特別適合用來測試一個函數在不同的參數輸入的情況下的性能。@Param注解接收一個String數組,在@setup方法執行前轉化為為對應的數據類型。多個@Param注解的成員之間是乘積關系,譬如有兩個用@Param注解的字段,第一個有5個值,第二個字段有2個值,那么每個測試方法會跑5*2=10次。

 

  6、參考資料

  https://blog.csdn.net/lxbjkben/article/details/79410740


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM