淺談Java基準(zhǔn)性能測(cè)試之JMH
一、JMH vs JMeter
JMeter可能是最常用的性能測(cè)試工具。它既支持圖形界面,也支持命令行,屬于黑盒測(cè)試的范疇,對(duì)非開(kāi)發(fā)人員比較友好,上手也非常容易。圖形界面一般用于編寫(xiě)、調(diào)試測(cè)試用例,而實(shí)際的性能測(cè)試建議還是在命令行下運(yùn)行。
很多場(chǎng)景下JMeter和JMH都可以做性能測(cè)試,但是對(duì)于嚴(yán)格意義上的基準(zhǔn)測(cè)試來(lái)說(shuō),只有JMH才適合。JMeter的測(cè)試結(jié)果精度相對(duì)JVM較低、所以JMeter不適合于類級(jí)別的基準(zhǔn)測(cè)試,更適合于對(duì)精度要求不高、耗時(shí)相對(duì)較長(zhǎng)的操作。
JMeter測(cè)試精度差: JMeter自身框架比較重,舉個(gè)例子:使用JMH測(cè)試一個(gè)方法,平均耗時(shí)0.01ms,而使用JMeter測(cè)試的結(jié)果平均耗時(shí)20ms,相差200倍。JMeter內(nèi)置很多采樣器:JMeter內(nèi)置了支持多種網(wǎng)絡(luò)協(xié)議的采樣器,可以在不寫(xiě)Java代碼的情況下實(shí)現(xiàn)很多復(fù)雜的測(cè)試。JMeter支持集群的方式運(yùn)行,方便模擬多用戶、高并發(fā)壓力測(cè)試。
總結(jié): JMeter適合一些相對(duì)耗時(shí)的集成功能測(cè)試,如API接口的測(cè)試。JMH適合于類或者方法的單元測(cè)試。
二、JMH基本用法
2.1、創(chuàng)建JMH項(xiàng)目
官方推薦為JMH基準(zhǔn)測(cè)試創(chuàng)建單獨(dú)的項(xiàng)目,最簡(jiǎn)單的創(chuàng)建JMH項(xiàng)目的方法就是基于maven項(xiàng)目原型的方式創(chuàng)建(如果是在windows環(huán)境下,需要對(duì)org.open.jdk.jmh這樣帶.的用雙引號(hào)包裹)。
mvn archetype:generate
-DinteractiveMode=false
-DarchetypeGroupId=org.openjdk.jmh
-DarchetypeArtifactId=jmh-java-benchmark-archetype
-DarchetypeVersion=1.21
-DgroupId=com.jenkov
-DartifactId=first-benchmark
-Dversion=1.0
可以看到生成的項(xiàng)目pom文件中主要是添加了兩個(gè)jmh
的依賴和設(shè)置了maven-shade-plugin的編譯方式(負(fù)責(zé)把項(xiàng)目的所有依賴jar包打入到目標(biāo)jar包中,與springboot的實(shí)現(xiàn)方式類似)。
<dependencies> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-core</artifactId> <version>${jmh.version}</version> </dependency> <dependency> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>${jmh.version}</version> <scope>provided</scope> </dependency> </dependencies> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.2</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <finalName>${uberjar.name}</finalName> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.openjdk.jmh.Main</mainClass> </transformer> </transformers> <filters> <filter> <!-- Shading signed JARs will fail without this. http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar --> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin>
生成的項(xiàng)目中已經(jīng)包含了一個(gè)class文件MyBenchmark.java,如下:
public class MyBenchmark { @Benchmark public void testMethod() { // This is a demo/sample template for building your JMH benchmarks. Edit as needed. // Put your benchmark code here. } }
2.2、編寫(xiě)基準(zhǔn)測(cè)試代碼
在上面生成的MyBenchmark類的testMethod中就可以添加基準(zhǔn)測(cè)試的java代碼,舉例如下:測(cè)試AtomicInteger的incrementAndGet的基準(zhǔn)性能。
public class MyBenchmark { static AtomicInteger integer = new AtomicInteger(); @Benchmark public void testMethod() { // This is a demo/sample template for building your JMH benchmarks. Edit as needed. // Put your benchmark code here. integer.incrementAndGet(); } }
2.3、JMH打包、運(yùn)行
項(xiàng)目打包
mvn clean install
運(yùn)行生成的目標(biāo)jar包benchmark.jar:
java -jar benchmark.jar
# JMH version: 1.21
# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13
# VM invoker: C:\Java\jdk1.8.0_181\jre\bin\java.exe
# VM options: <none>
# 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: org.sample.MyBenchmark.testMethod
# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration 1: 81052462.185 ops/s
# Warmup Iteration 2: 80152956.333 ops/s
# Warmup Iteration 3: 81305026.522 ops/s
# Warmup Iteration 4: 81740215.227 ops/s
# Warmup Iteration 5: 82398485.097 ops/s
Iteration 1: 82176523.804 ops/s
Iteration 2: 81818881.730 ops/s
Iteration 3: 82812749.807 ops/s
Iteration 4: 82406672.531 ops/s
Iteration 5: 74270344.512 ops/s
Result "org.sample.MyBenchmark.testMethod":
80697034.477 ±(99.9%) 13903555.960 ops/s [Average]
(min, avg, max) = (74270344.512, 80697034.477, 82812749.807), stdev = 3610709.330
CI (99.9%): [66793478.517, 94600590.437] (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 Mode Cnt Score Error Units
MyBenchmark.testMethod thrpt 5 80697034.477 ± 13903555.960 ops/s
從上面的日志我們大致可以了解到 JMH的基準(zhǔn)測(cè)試主要經(jīng)歷了下面幾個(gè)過(guò)程:
1.打印本次測(cè)試的配置,warmup:5輪;measurement:5輪;每輪:10s;啟動(dòng)1個(gè)線程做測(cè)試;基準(zhǔn)測(cè)試指標(biāo):吞吐量(throughput,單位是s);測(cè)試方法MyBenchmark.testMethod
2.啟動(dòng)一個(gè)JVM進(jìn)程做基準(zhǔn)測(cè)試(也可以設(shè)置啟動(dòng)多個(gè)進(jìn)程,減少隨機(jī)因素的誤差影響)
3.在JVM進(jìn)程中先執(zhí)行了5輪的預(yù)熱(warmup),每輪10s,總共50s的預(yù)熱時(shí)間。預(yù)熱的數(shù)據(jù)不作為基準(zhǔn)測(cè)試的參考。
4.測(cè)試了5輪,每輪10s,總共50s的測(cè)試時(shí)間
5.匯總測(cè)試數(shù)據(jù)、生成結(jié)果報(bào)表。最終結(jié)論是吞吐量(80697034.477 ±13903555.960 ops/s),其中80697034.477 是結(jié)果,13903555.960是誤差范圍。
2.4、JMH與Springboot
在對(duì)Springboot項(xiàng)目做JMH基準(zhǔn)測(cè)試時(shí)可能會(huì)因?yàn)閙aven-shade-plugin插件的問(wèn)題打包報(bào)錯(cuò),需要在JMH的maven-shade-plugin的插件配置中添加id即可。項(xiàng)目的pom可能如下:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.7.RELEASE</version> <relativePath/> </parent> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.2</version> <executions> <execution> <!-- 需要在此處添加一個(gè)id標(biāo)簽,否則mvn package時(shí)會(huì)報(bào)錯(cuò) --> <id>shade-all-dependency-jar</id> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> ... </configuration> </execution> </executions> </plugin> ... </project>
在測(cè)試代碼中正?;赟pringBootApplication構(gòu)建ConfigurableApplicationContext從而獲取bean的方式獲取對(duì)象測(cè)試即可。
public class StringRedisTemplateBenchmark { StringRedisTemplate redisTemplate; @Setup(Level.Trial) public void setUp() { redisTemplate = SpringApplication.run(SpringBootApplicationClass.class).getBean(StringRedisTemplate.class); } @Benchmark public void testGet() { redisTemplate.opsForValue().get("testkey"); } } @SpringBootApplication public class SpringBootApplicationClass { }
application.properties
lettuce.pool.maxTotal=50 lettuce.pool.maxIdle=10 lettuce.pool.minIdle=0 lettuce.sentinel.master=mymaster lettuce.sentinel.nodes=10.xx.xx.xx:26379,10.xx.xx.xx:26379 lettuce.password=xxxxxx
三、JMH注解
JMH測(cè)試的相關(guān)配置大多是通過(guò)注解的方式體現(xiàn)的。
具體每個(gè)注解的使用實(shí)例也可以參考官網(wǎng)
3.1、JMH Benchmark Modes
JMH benchmark支持如下幾種測(cè)試模式:
- Throughput: 吞吐量,測(cè)試每秒可以執(zhí)行操作的次數(shù)
- Average Time: 平均耗時(shí),測(cè)試單次操作的平均耗時(shí)
- Sample Time:采樣耗時(shí),測(cè)試單次操作的耗時(shí),包括最大、最小耗時(shí),已經(jīng)百分位耗時(shí)等
- Single Shot Time: 只計(jì)算一次的耗時(shí),一般用來(lái)測(cè)試?yán)鋯?dòng)的性能(不設(shè)置JVM預(yù)熱)
- All: 測(cè)試上面的所有指標(biāo)
默認(rèn)的benchmark mode是Throughput,可以通過(guò)注解的方式設(shè)置BenchmarkMode,注解支持放在類或方法上。如下所示設(shè)置了Throughput和SampleTime兩個(gè)Benchmark mode。
@BenchmarkMode({Mode.Throughput, Mode.SampleTime}) public class MyBenchmark { static AtomicInteger integer = new AtomicInteger(); @Benchmark public void testMethod() { // This is a demo/sample template for building your JMH benchmarks. Edit as needed. // Put your benchmark code here. integer.incrementAndGet(); } }
3.2、Benchmark Time Units
JMH支持設(shè)置打印基準(zhǔn)測(cè)試結(jié)果的時(shí)間單位,通過(guò)@OutputTimeUnit注解的方式設(shè)置。
@OutputTimeUnit(TimeUnit.SECONDS) public class MyBenchmark { static AtomicInteger integer = new AtomicInteger(); @Benchmark public void testMethod() { integer.incrementAndGet(); } }
3.3、Benchmark State
有時(shí)候我們?cè)谧龌鶞?zhǔn)測(cè)試的時(shí)候會(huì)需要使用一些變量、字段,@State注解是用來(lái)配置這些變量的生命周期,@State注解可以放在類上,然后在基準(zhǔn)測(cè)試方法中可以通過(guò)參數(shù)的方式把該類對(duì)象作為參數(shù)使用。@State支持的生命周期類型:
- Benchmark: 整個(gè)基準(zhǔn)測(cè)試的生命周期,多個(gè)線程共用同一份實(shí)例對(duì)象。該類內(nèi)部的@Setup @TearDown注解的方法可能會(huì)被任一個(gè)線程執(zhí)行,但是只會(huì)執(zhí)行一次。
- Group: 每一個(gè)Group內(nèi)部共享同一個(gè)實(shí)例,需要配合@Group @GroupThread使用。該類內(nèi)部的@Setup @TearDown注解的方法可能會(huì)該Group內(nèi)的任一個(gè)線程執(zhí)行,但是只會(huì)執(zhí)行一次。
- Thread:每個(gè)線程的實(shí)例都是不同的、唯一的。該類內(nèi)部的@Setup @TearDown注解的方法只會(huì)被當(dāng)前線程執(zhí)行,而且只會(huì)執(zhí)行一次。
被@State標(biāo)示的類必須滿足如下兩個(gè)要求:
類必須是public的
必須有無(wú)參構(gòu)造函數(shù)
3.4、State Object @Setup @TearDown
在@Scope注解標(biāo)示的類的方法上可以添加@Setup和@TearDwon注解。@Setup:用來(lái)標(biāo)示在Benchmark方法使用State對(duì)象之前需要執(zhí)行的操作。@TearDown:用來(lái)標(biāo)示在Benchmark方法之后需要對(duì)State對(duì)象執(zhí)行的操作。
如下示例:
@OutputTimeUnit(TimeUnit.SECONDS) public class MyBenchmark { @Benchmark public void testMethod(TestAddAndGetState state) { state.getInteger().incrementAndGet(); } @State(Scope.Benchmark) public static class TestAddAndGetState { private AtomicInteger integer; @Setup(Level.Iteration) public void setup() { integer = new AtomicInteger(); } public AtomicInteger getInteger() { return integer; } } }
@Setup、@TearDown支持設(shè)置Level級(jí)別,Level有三個(gè)值:
- Trial: 每次benchmark前/后執(zhí)行一次,每次benchmark會(huì)包含多輪(Iteration)
- Iteration: 每輪執(zhí)行前/后執(zhí)行一次
- Invocation: 每次調(diào)用測(cè)試的方法前/后都執(zhí)行一次,這個(gè)執(zhí)行頻率會(huì)很高,一般用不上。
3.5、Fork
@Fork注解用來(lái)設(shè)置啟動(dòng)的JVM進(jìn)程數(shù)量,多個(gè)進(jìn)程是串行的方式啟動(dòng)的,多個(gè)進(jìn)程可以減少偶發(fā)因素對(duì)測(cè)試結(jié)果的影響。
3.6、Thread
@Thread用來(lái)配置執(zhí)行測(cè)試啟動(dòng)的線程數(shù)量
3.7、Warmup
@Warmup 用來(lái)配置預(yù)熱的時(shí)間,如下所示配置預(yù)熱五輪,每輪1second,也就是說(shuō)總共會(huì)預(yù)熱5s左右,在這5s內(nèi)會(huì)不停的循環(huán)調(diào)用測(cè)試方法,但是預(yù)熱時(shí)的數(shù)據(jù)不作為測(cè)試結(jié)果參考。
@Warmup(iterations = 5, time = 1)
3.8、Measurement
@Measurement用來(lái)配置基準(zhǔn)測(cè)試的時(shí)間,如下所示配置預(yù)熱10輪,每輪1second,也就是說(shuō)總共會(huì)測(cè)試10s左右,在這10s內(nèi)會(huì)不停的循環(huán)調(diào)用測(cè)試方法,同事測(cè)試數(shù)據(jù)會(huì)被基準(zhǔn)測(cè)試結(jié)果參考。
@Measurement(iterations = 5, time = 1)
四、輸出測(cè)試結(jié)果
jmh支持多種格式的結(jié)果輸出text, csv, scsv, json, latex
如下打印出json格式的:
java -jar benchmark.jar -rf json
以上就是淺談Java基準(zhǔn)性能測(cè)試之JMH的詳細(xì)內(nèi)容,更多關(guān)于Java基準(zhǔn)性能測(cè)試 JMH的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java使用lambda自定義Arrays.sort排序規(guī)則說(shuō)明
這篇文章主要介紹了Java使用lambda自定義Arrays.sort排序規(guī)則說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05JDK1.8中的ConcurrentHashMap使用及場(chǎng)景分析
這篇文章主要介紹了JDK1.8中的ConcurrentHashMap使用及場(chǎng)景分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01Spring基于注解管理bean實(shí)現(xiàn)方式講解
很多時(shí)候我們需要根據(jù)不同的條件在容器中加載不同的Bean,或者根據(jù)不同的條件來(lái)選擇是否在容器中加載某個(gè)Bean,這就是Bean的加載控制,一般我們可以通過(guò)編程式或注解式兩種不同的方式來(lái)完成Bean的管理2023-01-01SpringBoot 使用 FTP 操作文件的過(guò)程(刪除、上傳、下載文件)
這篇文章主要介紹了SpringBoot 使用 FTP 操作文件,主要包括配置ftp服務(wù)器,上傳、刪除、下載文件操作,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12java后臺(tái)判斷客戶端是手機(jī)/PC并返回不同頁(yè)面的實(shí)例
下面小編就為大家分享一篇java后臺(tái)判斷客戶端是手機(jī)/PC并返回不同頁(yè)面的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-01-01簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系
這篇文章主要介紹了簡(jiǎn)單了解JAVA內(nèi)存泄漏和溢出區(qū)別及聯(lián)系,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03解析Java的可變長(zhǎng)參數(shù)列表及其使用時(shí)的注意點(diǎn)
這篇文章主要介紹了解析Java的可變參數(shù)列表及其使用時(shí)的注意點(diǎn),注意可變參數(shù)必須位于最后一項(xiàng),需要的朋友可以參考下2016-03-03