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

結果查看
打開結果文件,前面一大坨是系統(tǒng)信息,可以簡單看看,直接拉到最后,結果如下:
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
結果如上,Mode中thrpt代表吞吐量,單位時間內的執(zhí)行次數(shù)。avgt是平均時間,一次執(zhí)行需要的單位時間。sample是基于采樣的執(zhí)行時間,采樣頻率由JMH自動控制。ss是單次執(zhí)行的時間。
從結果上看,兩種排序算法的性能相差無幾,當然與我們的邏輯太簡單也有關系。這次的分享就到這里,大家趕緊用到自己的項目中,測試一下吧。
? 作者?|??耶low
來源 |??cnblogs.com/lbhym/p/15363846.html

