轉載自:https://lotabout.me/2019/UUID-Generator-Benchmark/
敘述
在 Java 中,我們常用 UUID.randomUUID()
來隨機生成一個 UUID。但在某些極端的情況下,它的性能可能滿足不了你的要求(雖然幾乎不可能出現)。這裏我們測試了 4 種 UUID 生成器的性能。
測試結果
最終測試的結果如下(雖然只看到 3 根線,但其實有 4 根,其中藍線 UUID.randomUUID 與綠線 jugWithSecureRandom 幾乎重合):
可以看到:
- 在單線程時,jugWithRandom 遠遠超過其它的生成器,而 jugTime 次之。
- 隨着線程的增加,各生成器的吞吐均有所下降。
- randomUUID 中使用 SecureRandom 來獲取隨機數,而它是通過獲取操作系統的一些隨機噪聲來生成隨機數的,所以是安全的,但性能卻不是很好(相對)。
- 所有這些生成器都是線程安全的,換句話說內部會做線程同步,因此線程增加,吞吐會下降。
- 其中 Random 是用 CAS 來完成同步,其餘均使用
synchronized
,理論上高併發下,線程數越多,Random 的性能越差,而其它則幾乎不變。
- 注意 jugTime 在單線程時吞吐接近 1w/ms,這也是基於時間的 UUID 每毫秒能擁有的最大數值(參考uuid-timebased 說明)。
- jugTime 的生成器的性能幾乎總是優於
randomUUID
。 - 不過現實中,不太能遇到有場景需要有 1k/ms 這樣的吞吐需求。
測試設置
這裏我們測試了 java 內置的 UUID.randomUUID()
, java-uuid-generator 的 TimeBasedGenerator
和 RandomBasedGenerator
,而其中隨機數發生器分別選用 Random
和 SecureRandom
。測試代碼 如下:
@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 5)
public class MyBenchmark {
private RandomBasedGenerator randomBasedGenerator;
private RandomBasedGenerator jugRandomGenerator;
private TimeBasedGenerator timeBasedGenerator;
@Setup
public void init() {
randomBasedGenerator = Generators.randomBasedGenerator();
timeBasedGenerator = Generators.timeBasedGenerator();
jugRandomGenerator = Generators.randomBasedGenerator(new Random());
}
@Benchmark
public void UUIDRandomUUID(Blackhole bh) {
bh.consume(UUID.randomUUID());
}
@Benchmark
public void jugWithRandom(Blackhole bh) {
bh.consume(jugRandomGenerator.generate());
}
@Benchmark
public void jugWithSecureRandom(Blackhole bh) {
bh.consume(randomBasedGenerator.generate());
}
@Benchmark
public void jugTime(Blackhole bh) {
bh.consume(timeBasedGenerator.generate());
}
}
測試框架使用 Jmh。測試使用 jdk 1.8 在 8C MacBook Pro 下完成,分別測試了 1,2,4,8
個線程下的吞吐。
寫在後面
這個測試的起因是產品在壓測的時候發現 UUID 生成佔 Running 線程較大的部分,且 JProfiler 的線程圖中有許多線程是 Blocking 的狀態,因此猜測是 UUID.randomUUID中的synchronized 導致線程同步慢,所以想找一些替代的生成器。最後發現 UUID.randomUUID 的吞吐並不是什麼大的問題,但也很慶幸做了這個測試,瞭解了 UUID 生成器的能力,還有 Time-Based UUID 也是一個不錯的選擇。