Using JMH to Benchmark GUID Generation Strategies in Java
This article introduces JMH, explains its key features, and presents microbenchmark results comparing thread‑exclusive, thread‑shared, Snowflake, UUID, and Snowflake‑algorithm GUID generation methods under various thread counts, accompanied by the full Java test code.
In a previous post I discussed several common ways to generate globally unique identifiers (GUIDs) for performance testing, but without real test data the conclusions were speculative. To obtain concrete measurements I turned to the Java Microbenchmark Harness (JMH), a widely used tool for writing and running Java microbenchmarks.
JMH Overview
JMH provides high‑confidence results by eliminating noise and bias, offers a rich set of annotations and APIs for ease of use, supports multiple benchmark modes (throughput, latency, etc.), allows custom environment settings such as GC strategies, and is extensively used throughout the Java ecosystem, including the JDK itself.
Experimental Setup
Four GUID generation schemes (excluding a distributed service‑based approach) were benchmarked: thread‑exclusive counter, thread‑shared atomic counter, Snowflake algorithm, and Java UUID.randomUUID() . Each benchmark was warmed up twice with a batch size of 2, then measured once with a batch size of 1. The JMH configuration used was:
.warmupIterations(2) // pre‑warm count
.warmupBatchSize(2) // pre‑warm batch size
.measurementIterations(1) // measurement count
.measurementBatchSize(1) // measurement batch size
.build();Results were collected for 1, 10, and 40 concurrent threads on a 12‑core machine. The throughput (operations per microsecond) showed that both the thread‑exclusive and thread‑shared counters far outperformed UUID and the Snowflake algorithm.
Single‑Thread Results
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/us10‑Thread Results
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/us40‑Thread Results
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/usThe performance scales similarly from 10 to 40 threads, indicating the CPU was near full utilization. Multiplying the reported microseconds by one million yields the per‑second operation count, which can guide selection of a GUID generation strategy.
Test Case Code
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)
@State(Scope.Thread)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
public class UniqueNumberTest {
SnowflakeUtils snowflakeUtils = new SnowflakeUtils(1, 1);
ThreadLocal
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")
.resultFormat(ResultFormatType.JSON)
.forks(1)
.threads(40)
.warmupIterations(2)
.warmupBatchSize(2)
.measurementIterations(1)
.measurementBatchSize(1)
.build();
new Runner(options).run();
}
}Note: JMH currently does not support Groovy, so the benchmark was written in Java.
FunTester
10k followers, 1k articles | completely useless
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.