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

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

但對於一個性能測試工程師來講,有真是測試數據才更有說服力。這讓我想起來之前學過的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();  
    }  
  
  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章