更準(zhǔn)確的測試Java程序性能——JMH基準(zhǔn)測試
什么是JMH
JMH,即Java Microbenchmark Harness,Java平臺下的一套微基準(zhǔn)測試工具。如果我們需要測試API性能的話,就可以用上這個(gè)工具,所以它并不是取代單元測試的。它可以在開發(fā)階段提供性能參考標(biāo)準(zhǔn),不過這并不代表線上的性能表現(xiàn),不同的硬件和操作系統(tǒng)也會(huì)帶來性能差異,所以最終還是需要上到測試或沙箱環(huán)境,讓測試人員進(jìn)行壓測。
為什么需要JMH
在了解JMH之前,如果需要性能測試,我們通常會(huì)使用for循環(huán),或者JMeter。而JMH正是比for循環(huán)嚴(yán)謹(jǐn),比JMeter使用簡單的測試工具。
再者,不知道你注意過沒有,在使用for循環(huán)測試時(shí),第一次或者頭幾次運(yùn)行總是最慢的,越到后面越快。從《計(jì)算機(jī)組成與設(shè)計(jì) 硬件軟件接口》一書中可以了解到,從更底層講,Java是解釋型的語言。雖然Java也需要編譯,但是編譯后只是字節(jié)碼,還需要JVM解釋成對應(yīng)宿主機(jī)的機(jī)器碼。解釋的優(yōu)勢是可移植性,但是性能較差。在20世紀(jì)80和90年代,雖然解釋型語言的性能也飛速提升,但是與C語言相比,仍有10倍的性能差距。
?為了保持可移植性,同時(shí)又提高性能,Java便開發(fā)了即時(shí)編譯器(Just In Time complier),其通過記錄運(yùn)行的程序來找到所謂的“熱點(diǎn)”方法,然后將它們直接編譯成宿主機(jī)的指令序列,即不通過JVM解釋那一層。這樣以后該方法的運(yùn)行就會(huì)更快。
看到這里也就明白了,為什么程序越到后面就會(huì)越快。JMH在真正的測試之前會(huì)預(yù)熱程序,而且還可以通過配置進(jìn)程數(shù)、線程數(shù)等參數(shù)來使程序更接近實(shí)際的運(yùn)行狀況。
如何使用
首先引入Maven依賴:
??????
????????????org.openjdk.jmh
????????????jmh-core
????????????1.21
????????
????????
????????????org.openjdk.jmh
????????????jmh-generator-annprocess
????????????1.21
????????????test
????????
本案例中,我寫了一個(gè)簡單的小程序,它會(huì)從指定目錄讀取文件夾內(nèi)容(每行一個(gè)數(shù)字),然后會(huì)對取出來的數(shù)字進(jìn)行排序。排序算法選擇了插入排序和歸并排序,我們通過基準(zhǔn)測試來看看兩者的性能差距。
讀取文件內(nèi)容
public?class?ReadFile?{
???public?static?int[]?readInteger(String?path){
???????try(BufferedReader?in?=?new?BufferedReader(new?FileReader(path));)?{
???????????List?temp?=?new?ArrayList<>();
???????????String?str;
???????????while?((str?=?in.readLine())?!=?null)?{
???????????????temp.add(Integer.parseInt(str));
???????????}
???????????int[]?result?=?new?int[temp.size()];
???????????for(int?i=0;i???????????????result[i]=temp.get(i);
???????????}
???????????return?result;
???????}?catch?(Exception?e)?{
???????????e.printStackTrace();
???????????return?new?int[0];
???????}
???}
}
兩個(gè)排序算法就不貼了,網(wǎng)上可以搜到很多。實(shí)際的開發(fā)可能會(huì)用上SpringBoot,所以還得與Junit整合,并使用自動(dòng)注入功能。先直接貼上測試代碼:
@BenchmarkMode(Mode.All)
@Warmup(iterations?=?3)//預(yù)熱輪數(shù)
@Measurement(iterations?=?1,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)
@Threads(8)//線程數(shù)
@Fork(0)?//fork的次數(shù),如果想用Autowired自動(dòng)注入,這個(gè)填0
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@RunWith(SpringRunner.class)?//整合SpringBoot的測試運(yùn)行環(huán)境
@SpringBootTest
public?class?JHMTest?{
???//想用自動(dòng)注入功能,對象必須是靜態(tài)的,fork填0
????private?static?SortService?service;
????@Autowired
????void?setService(SortService?service){
????????JHMTest.service?=service;
????}
????@Test
????public?void?executeBenchmark()?throws?RunnerException?{
????????//JMH的選項(xiàng)配置,除了上面的注解方式的配置,也可以直接在這個(gè)Options里面配置。
???????//其中/Users/xxxx/Desktop/Benchmark.json是結(jié)果的輸出文件
????????Options?options?=?new?OptionsBuilder().include(this.getClass().getSimpleName())
????????????????.output("/Users/xxxx/Desktop/Benchmark.json").build();
????????new?Runner(options).run();
????}
????@Benchmark
????public?void?insertSortTest(){
????????int[]?arr?=?ReadFile.readInteger("/Users/xxxxx/Desktop/test.txt");
????????service.insertSort(arr);
????}
????@Benchmark
????public?void?mergeSortTest(){
????????int[]?arr?=?ReadFile.readInteger("/Users/xxxxx/Desktop/test?2.txt");
????????service.mergeSort(arr);
????}
}
上面注釋簡單寫了幾個(gè)關(guān)鍵點(diǎn),我們執(zhí)行executeBenchmark方法,JMH就會(huì)執(zhí)行該類下帶有Benchmark注解的方法。最終結(jié)果會(huì)輸出到指定文件中。
其他注解的解釋可見圖

結(jié)果查看
打開結(jié)果文件,前面一大坨是系統(tǒng)信息,可以簡單看看,直接拉到最后,結(jié)果如下:
Benchmark????????????????????????????????????????Mode?????Cnt????Score???Error???Units
JHMTest.insertSortTest??????????????????????????thrpt??????????129.302??????????ops/ms
JHMTest.mergeSortTest???????????????????????????thrpt??????????122.224??????????ops/ms
JHMTest.insertSortTest???????????????????????????avgt????????????0.065???????????ms/op
JHMTest.mergeSortTest????????????????????????????avgt????????????0.066???????????ms/op
JHMTest.insertSortTest?????????????????????????sample??122410????0.066?±?0.002???ms/op
JHMTest.insertSortTest:insertSortTest·p0.00????sample????????????0.014???????????ms/op
JHMTest.insertSortTest:insertSortTest·p0.50????sample????????????0.050???????????ms/op
JHMTest.insertSortTest:insertSortTest·p0.90????sample????????????0.106???????????ms/op
JHMTest.insertSortTest:insertSortTest·p0.95????sample????????????0.120???????????ms/op
JHMTest.insertSortTest:insertSortTest·p0.99????sample????????????0.192???????????ms/op
JHMTest.insertSortTest:insertSortTest·p0.999???sample????????????0.492???????????ms/op
JHMTest.insertSortTest:insertSortTest·p0.9999??sample???????????11.891???????????ms/op
JHMTest.insertSortTest:insertSortTest·p1.00????sample???????????17.334???????????ms/op
JHMTest.mergeSortTest??????????????????????????sample??122055????0.066?±?0.002???ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.00??????sample????????????0.014???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.50??????sample????????????0.050???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.90??????sample????????????0.107???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.95??????sample????????????0.121???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.99??????sample????????????0.187???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.999?????sample????????????0.457???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p0.9999????sample???????????11.957???????????ms/op
JHMTest.mergeSortTest:mergeSortTest·p1.00??????sample???????????12.419???????????ms/op
JHMTest.insertSortTest?????????????????????????????ss????????????0.020???????????ms/op
JHMTest.mergeSortTest??????????????????????????????ss????????????0.020???????????ms/op
結(jié)果如上,Mode中thrpt代表吞吐量,單位時(shí)間內(nèi)的執(zhí)行次數(shù)。avgt是平均時(shí)間,一次執(zhí)行需要的單位時(shí)間。sample是基于采樣的執(zhí)行時(shí)間,采樣頻率由JMH自動(dòng)控制。ss是單次執(zhí)行的時(shí)間。
從結(jié)果上看,兩種排序算法的性能相差無幾,當(dāng)然與我們的邏輯太簡單也有關(guān)系。這次的分享就到這里,大家趕緊用到自己的項(xiàng)目中,測試一下吧。
? 作者?|??耶low
來源 |??cnblogs.com/lbhym/p/15363846.html

