前文分享了几种性能测试中常用到的生成全局唯一标识的案例,虽然在文中我猜测了几种方案设计的性能,并根据自己的经验给出了适用的场景。

但对于一个性能测试工程师来讲,有真是测试数据才更有说服力。这让我想起来之前学过的 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();  
    }  


}


↙↙↙阅读原文可查看相关链接,并与作者交流