最近在手写一个ID生成器,需要比较UUID
和目前比较流行的 NanoID
之间的速度差异,当然也要测一下根据规则自创的ID生成器。
这样的代码属于最基础的API,速度哪怕减上几纳秒,累加起来也是很可观的。关键是,我该如何评估ID的生成速度呢?
1. 如何统计性能?
常见的方法,是写一些统计代码。这些代码,穿插在我们的逻辑中,进行一些简单的计时运算。比如下面这几行:
long start = System.currentTimeMillis();
//logic
long cost = System.currentTimeMillis() - start;
System.out.println("Logic cost : " + cost);
复制代码
这样的统计方式,用在业务代码里,哪怕是APM里,并不见得有什么问题。
可惜的是,这段代码的统计结果,并不见得一定准确。举个例子来说,JVM在执行时,会对一些代码块,或者一些频繁执行的逻辑,进行JIT编译和内联优化,在得到一个稳定的测试结果之前,需要先循环上上万次
,进行预热。预热前和预热后的性能差别是非常大的。
另外,评估性能,有很多的指标。如果这些指标数据,每次都要手工去算的话,那肯定是枯燥乏味且低效的。
JMH(the Java Microbenchmark Harness) 就是这样一个能够做基准测试的工具。如果你通过我们一系列的工具,定位到了热点代码,要测试它的性能数据,评估改善情况,就可以交给JMH。它的测量精度非常高,最高可达到纳秒的级别。
JMH已经在JDK 12中被包含,其他版本的需要自行引入maven,坐标如下。
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.23</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.23</version>
<scope>provided</scope>
</dependency>
</dependencies>
复制代码
下面,我们介绍一下这个工具的使用。
2. 关键注解
JMH
是一个jar包,它和单元测试框架JUnit
非常的像,可以通过注解进行一些基础配置。这部分配置有很多是可以通过main方法的OptionsBuilder
进行设置的。
上图是一个典型的JMH程序执行的内容。通过开启多个进程,多个线程,首先执行预热,然后执行迭代,最后汇总所有的测试数据进行分析。在执行前后,还可以根据粒度处理一些前置和后置操作。
一个简单的代码如下:
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Thread)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(2)
public class BenchmarkTest {
@Benchmark
public long shift() {
long t = 455565655225562L;
long a = 0;
for (int i = 0; i < 1000; i++) {
a = t >> 30;
}
return a;
}
@Benchmark
public long div() {
long t = 455565655225562L;
long a = 0;
for (int i = 0; i < 1000; i++) {
a = t / 1