效能測試中唯一標識的 JMH 測試

FunTester發表於2024-04-15

前文分享了幾種效能測試中常用到的生成全域性唯一標識的案例,雖然在文中我猜測了幾種方案設計的效能,並根據自己的經驗給出了適用的場景。

但對於一個效能測試工程師來講,有真是測試資料才更有說服力。這讓我想起來之前學過的 Java 微基準測試框架 JMH ,所以不妨一試。

JMH 簡介

JMH (Java Microbenchmark Harness) 是一個用於編寫和執行 Java 基準測試的工具。它被廣泛用於評估 Java 應用程式的效能,並幫助開發人員發現和最佳化效能瓶頸。

JMH 的主要特點包括:

  1. 高可信度:JMH 提供了多種機制來消除測試過程中的噪音和偏差,確保測試結果的可靠性。
  2. 易用性:JMH 提供了豐富的註解和 API,使編寫和執行基準測試變得相對簡單。
  3. 靈活性:JMH 支援多種測試模式,如簡單的吞吐量測試、微基準測試以及更復雜的測試場景。
  4. 可擴充套件性:JMH 允許使用者自定義測試環境,如 GC 策略、編譯器選項等,以滿足特定的效能評估需求。
  5. 廣泛應用:JMH 被廣泛應用於 Java 生態系統中,包括 JDK 自身的效能最佳化、第三方開源庫的效能評估等。

JMH 是 Java 開發者評估應用程式效能的強大工具,有助於提高 Java 應用程式的整體質量和效能。同樣地對於效能測試而言,也可以透過 JMH 測試評估一段程式碼在實際執行當中的表現。

實測

除了 使用分散式服務生成GUID 這個方案以外,其他四種方案(其中兩種是我自己常用的)均參與測試。原因是分散式服務需要網路互動,這個一聽就不高效能,還有我暫時沒條件測試這個。

下面有限展示實測結果,總結使用執行緒共享和執行緒獨享的方案效能均遠遠高於 UUID雪花演算法 。為了省事兒以下測試均預熱 2 次,預熱批次大小 2,測試迭代次數 1 次,迭代批次大小也是 1 次。配置如下:

.warmupIterations(2)//預熱次數
.warmupBatchSize(2)//預熱批次大小
.measurementIterations(1)//測試迭代次數
.measurementBatchSize(1)//測試批次大小
.build();

PS:JMH 貌似還不支援 Groovy 所以我用 Java 寫了這個用例。

下面是執行 1 個執行緒的測試結果:

UniqueNumberTest.exclusive  thrpt       203.146          ops/us
UniqueNumberTest.share      thrpt        99.860          ops/us
UniqueNumberTest.snow       thrpt         4.096          ops/us
UniqueNumberTest.uuid       thrpt        11.758          ops/us

下面是執行 10 個執行緒的測試結果:

Benchmark                    Mode  Cnt     Score   Error   Units
UniqueNumberTest.exclusive  thrpt       1117.347          ops/us
UniqueNumberTest.share      thrpt        670.141          ops/us
UniqueNumberTest.snow       thrpt         10.925          ops/us
UniqueNumberTest.uuid       thrpt          3.608          ops/us

PS:此時機器的效能基本跑滿了。

下面是 40 個執行緒的測試結果:

Benchmark                    Mode  Cnt     Score   Error   Units
UniqueNumberTest.exclusive  thrpt       1110.273          ops/us
UniqueNumberTest.share      thrpt        649.350          ops/us
UniqueNumberTest.snow       thrpt          8.908          ops/us
UniqueNumberTest.uuid       thrpt          4.205          ops/us

可以看出跟 10 個執行緒結果差不多。

本機配置 12 核心,以上的測試結果單位是微秒,把結果乘以 100 萬就是每秒的處理量,各位在使用不同方案時可以適當參考。

測試用例

下面是我的測試用例,測試結果我就不進行視覺化了。

package com.funtest.jmh;  

import com.funtester.utils.SnowflakeUtils;  
import org.openjdk.jmh.annotations.*;  
import org.openjdk.jmh.infra.Blackhole;  
import org.openjdk.jmh.results.format.ResultFormatType;  
import org.openjdk.jmh.runner.Runner;  
import org.openjdk.jmh.runner.RunnerException;  
import org.openjdk.jmh.runner.options.Options;  
import org.openjdk.jmh.runner.options.OptionsBuilder;  

import java.util.UUID;  
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.atomic.AtomicInteger;  

@BenchmarkMode(Mode.Throughput)  
//@Warmup(Ω = 3, time = 2, timeUnit = TimeUnit.SECONDS)//預熱次數,含義是每個測試會跑多久  
//@Measurement(iterations = 3, time = 5, timeUnit = TimeUnit.SECONDS)//測試迭代次數,含義是每個測試會跑多久  
//@Threads(1)//測試執行緒數  
//@Fork(2)//fork表示每個測試會fork出幾個程序,也就是說每個測試會跑幾次  
@State(value = Scope.Thread)//預設為Scope.Thread,含義是每個執行緒都會有一個例項  
@OutputTimeUnit(TimeUnit.MICROSECONDS)  
public class UniqueNumberTest {  

    SnowflakeUtils snowflakeUtils = new SnowflakeUtils(1, 1);  

    ThreadLocal<Integer> exclusive = ThreadLocal.withInitial(() -> 0);  

    AtomicInteger share = new AtomicInteger(0);  

    @Benchmark  
    public void uuid() {  
        UUID.randomUUID();  
    }  

    @Benchmark  
    public void snow() {  
        snowflakeUtils.nextId();  
    }  

    @Benchmark  
    public void exclusive(Blackhole blackhole) {  
        Integer i = exclusive.get();  
        i++;  
        blackhole.consume(i + "");  
    }  

    @Benchmark  
    public void share(Blackhole blackhole) {  
        blackhole.consume(share.incrementAndGet() + "");  
    }  

    public static void main(String[] args) throws RunnerException {  
        Options options = new OptionsBuilder()  
                .include(UniqueNumberTest.class.getSimpleName())//測試類名  
                .result("long/result.json")//測試結果輸出到result.json檔案  
                .resultFormat(ResultFormatType.JSON)//輸出格式  
                .forks(1)//fork表示每個測試會fork出幾個程序,也就是說每個測試會跑幾次  
                .threads(40)//測試執行緒數  
                .warmupIterations(2)//預熱次數  
                .warmupBatchSize(2)//預熱批次大小  
                .measurementIterations(1)//測試迭代次數  
                .measurementBatchSize(1)//測試批次大小  
                .build();  
        new Runner(options).run();  
    }  


}
  • 2021 年原創合集
  • 2022 年原創合集
  • 2023 年原創合集
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI 自動化
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章