一起來了解一下針對代碼級別的性能測試工具 JMH吧!

正文開始

JMH,即Java Microbenchmark Harness,這是專門用于進行代碼的微基準測試的一套工具API。

JMH 由 OpenJDK/Oracle 里面那群開發(fā)了 Java 編譯器的大牛們所開發(fā) 。何謂 Micro Benchmark 呢? 簡單地說就是在 method 層面上的 benchmark,精度可以精確到微秒級。

Java的基準測試需要注意的幾個點:

  • 測試前需要預(yù)熱。

  • 防止無用代碼進入測試方法中。

  • 并發(fā)測試。

  • 測試結(jié)果呈現(xiàn)。

比較典型的使用場景:

  1. 當你已經(jīng)找出了熱點函數(shù),而需要對熱點函數(shù)進行進一步的優(yōu)化時,就可以使用 JMH 對優(yōu)化的效果進行定量的分析。

  2. 想定量地知道某個函數(shù)需要執(zhí)行多長時間,以及執(zhí)行時間和輸入 n 的相關(guān)性

  3. 一個函數(shù)有兩種不同實現(xiàn)(例如JSON序列化/反序列化有Jackson和Gson實現(xiàn)),不知道哪種實現(xiàn)性能更好

盡管 JMH 是一個相當不錯的 Micro Benchmark Framework,但很無奈的是網(wǎng)上能夠找到的文檔比較少,而官方也沒有提供比較詳細的文檔,對使用造成了一定的障礙。 但是有個好消息是官方的 Code Sample 寫得非常淺顯易懂, 推薦在需要詳細了解 JMH 的用法時可以通讀一遍——本文則會介紹 JMH 最典型的用法和部分常用選項。

第一個例子

添加maven依賴

如果使用maven項目,只需要添加如下依賴:

編寫性能測試

執(zhí)行方式

這個代碼里面有好多注解,你第一次見可能不知道什么意思。先不用管,我待會一一介紹。

我們來運行這個測試,運行JMH基準測試有多種方式,一個是生成jar文件執(zhí)行, 一個是直接寫main函數(shù)或?qū)憜卧獪y試執(zhí)行。

一般對于大型的測試,需要測試時間比較久,線程比較多的話,就需要去寫好了丟到linux程序里執(zhí)行, 不然本機執(zhí)行很久時間什么都干不了了。

先編譯打包之后,然后執(zhí)行就可以了。當然在執(zhí)行的時候可以輸入-h參數(shù)來看幫助。

另外如果對于一些小的測試,比如我寫的上面這個小例子,在IDE里面就可以完成了,丟到linux上去太麻煩。 這時候可以在里面添加一個main函數(shù)如下:

這里其實也比較簡單,new個Options,然后傳入要運行哪個測試,選擇基準測試報告輸出文件地址,然后通過Runner的run方法就可以跑起來了。

報告結(jié)果

我們跑一下這個基準測試,完成后打開 E:/Benchmark.log,結(jié)果如下:

仔細看,三大部分,第一部分是字符串用加號連接執(zhí)行的結(jié)果,第二部分是StringBuilder執(zhí)行的結(jié)果,第三部分就是兩個的簡單結(jié)果比較。這里注意我們forks傳的2,所以每個測試有兩個fork結(jié)果。

前兩部分是一樣的,簡單說下。首先會寫出每部分的一些參數(shù)設(shè)置,然后是預(yù)熱迭代執(zhí)行(Warmup Iteration), 然后是正常的迭代執(zhí)行(Iteration),最后是結(jié)果(Result)。這些看看就好,我們最關(guān)注的就是第三部分, 其實也就是最終的結(jié)論。千萬別看歪了,他輸出的也確實很不爽,error那列其實沒有內(nèi)容,score的結(jié)果是xxx ± xxx,單位是每毫秒多少個操作。可以看到,StringBuilder的速度還確實是要比String進行文字疊加的效率好太多。

注解介紹

好了,當你對JMH有了一個基本認識后,現(xiàn)在來詳細解釋一下前面代碼中的各個注解含義。

@BenchmarkMode

基準測試類型。這里選擇的是Throughput也就是吞吐量。根據(jù)源碼點進去,每種類型后面都有對應(yīng)的解釋,比較好理解,吞吐量會得到單位時間內(nèi)可以進行的操作數(shù)。

  • Throughput: 整體吞吐量,例如“1秒內(nèi)可以執(zhí)行多少次調(diào)用”。

  • AverageTime: 調(diào)用的平均時間,例如“每次調(diào)用平均耗時xxx毫秒”。

  • SampleTime: 隨機取樣,最后輸出取樣結(jié)果的分布,例如“99%的調(diào)用在xxx毫秒以內(nèi),99.99%的調(diào)用在xxx毫秒以內(nèi)”

  • SingleShotTime: 以上模式都是默認一次 iteration 是 1s,唯有 SingleShotTime 是只運行一次。往往同時把 warmup 次數(shù)設(shè)為0,用于測試冷啟動時的性能。

  • All(“all”, “All benchmark modes”);

@Warmup

上面我們提到了,進行基準測試前需要進行預(yù)熱。一般我們前幾次進行程序測試的時候都會比較慢, 所以要讓程序進行幾輪預(yù)熱,保證測試的準確性。其中的參數(shù)iterations也就非常好理解了,就是預(yù)熱輪數(shù)。

為什么需要預(yù)熱?因為 JVM 的 JIT 機制的存在,如果某個函數(shù)被調(diào)用多次之后,JVM 會嘗試將其編譯成為機器碼從而提高執(zhí)行速度。所以為了讓 benchmark 的結(jié)果更加接近真實情況就需要進行預(yù)熱。

@Measurement

度量,其實就是一些基本的測試參數(shù)。

  1. iterations 進行測試的輪次

  2. time 每輪進行的時長

  3. timeUnit 時長單位

都是一些基本的參數(shù),可以根據(jù)具體情況調(diào)整。一般比較重的東西可以進行大量的測試,放到服務(wù)器上運行。

@Threads

每個進程中的測試線程,這個非常好理解,根據(jù)具體情況選擇,一般為cpu乘以2。

@Fork

進行 fork 的次數(shù)。如果 fork 數(shù)是2的話,則 JMH 會 fork 出兩個進程來進行測試。

@OutputTimeUnit

這個比較簡單了,基準測試結(jié)果的時間類型。一般選擇秒、毫秒、微秒。

@Benchmark

方法級注解,表示該方法是需要進行 benchmark 的對象,用法和 JUnit 的 @Test 類似。

@Param

屬性級注解,@Param 可以用來指定某項參數(shù)的多種情況。特別適合用來測試一個函數(shù)在不同的參數(shù)輸入的情況下的性能。

@Setup

方法級注解,這個注解的作用就是我們需要在測試之前進行一些準備工作,比如對一些數(shù)據(jù)的初始化之類的。

@TearDown

方法級注解,這個注解的作用就是我們需要在測試之后進行一些結(jié)束工作,比如關(guān)閉線程池,數(shù)據(jù)庫連接等的,主要用于資源的回收等。

@State

當使用@Setup參數(shù)的時候,必須在類上加這個參數(shù),不然會提示無法運行。

State 用于聲明某個類是一個“狀態(tài)”,然后接受一個 Scope 參數(shù)用來表示該狀態(tài)的共享范圍。 因為很多 benchmark 會需要一些表示狀態(tài)的類,JMH 允許你把這些類以依賴注入的方式注入到 benchmark 函數(shù)里。Scope 主要分為三種。

  1. Thread: 該狀態(tài)為每個線程獨享。

  2. Group: 該狀態(tài)為同一個組里面所有線程共享。

  3. Benchmark: 該狀態(tài)在所有線程間共享。

關(guān)于State的用法,官方的 code sample 里有比較好的例子。

第二個例子

再來看一個更常規(guī)一點性能測試的例子,

計算 1 ~ n 之和,比較串行算法和并行算法的效率,看 n 在大約多少時并行算法開始超越串行算法

首先定義一個表示這兩種實現(xiàn)的接口:

具體的兩種實現(xiàn)代碼我就不貼了,主要說明一下串行算法和并行算法實現(xiàn)原理:

  • 串行算法:使用 for-loop 來計算 n 個正整數(shù)之和。

  • 并行算法:將所需要計算的 n 個正整數(shù)分成 m 份,交給 m 個線程分別計算出和以后,再把它們的結(jié)果相加。

進行 benchmark 的代碼如下:

看到這里還沒過癮,那么就來群里與更多的大佬交流切磋技術(shù),戳這里:咱們來一起抱團取暖,好嗎?

我在自己的筆記本電腦上跑下來的結(jié)果,總數(shù)在10000時并行算法不如串行算法, 總數(shù)達到100000時并行算法開始和串行算法接近,總數(shù)達到1000000時并行算法所耗時間約是串行算法的一半左右。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容