Jmh
Skill umit/skills/skills/jmh
Write Java microbenchmarks with JMH (Java Microbenchmark Harness) that produce trustworthy numbers — not numbers distorted by JIT dead-code elimination, constant folding, insufficient warmup, or single-fork JIT contamination. Use this skill whenever the user writes `@Benchmark`, mentions JMH, microbenchmark, throughput measurement, latency measurement, or compares two implementations performance-wise. Triggers on `@Benchmark`, `@Setup`, `@State`, `Blackhole`, `@Fork`, `@Warmup`, `@Measurement`, `@OperationsPerInvocation`, `Mode.Throughput`, `Mode.AverageTime`, `Mode.SingleShotTime`, `Mode.SampleTime`, `BenchmarkMode`, `OutputTimeUnit`, `BenchmarkRunner`, `jmh-core`, `jmh-generator-annprocess`, `org.openjdk.jmh`, `me.champeau.jmh` (Gradle plugin), `pl.allegro.tech.build.axion-release`, `JMHTask`. Treat any benchmark without `Blackhole.consume()`, with `@Fork(0)`, or with `@Warmup(iterations < 5)` as broken by default — pre-flight statically before running. Pair with `async-profiler` (`-prof async`) for per-benchmark flame graphs to answer not just "which is faster" but "why".From its SKILL.md
npx -y skills add umit/skills --skill jmhAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
3 things to look at
- 5 stars5 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
- runs commandsInstructs the agent to run 8 commands, including `mvn clean verify -DskipTests` and 7 more.
- fetches URLsInstructs the agent to fetch 1 URL, including https://jmh.morethan.io.
SKILL.md
5.3 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
JMH — trustworthy Java microbenchmarks
Workflow
- Pre-flight before running — read
references/pitfalls.mdand apply its 15-point checklist to the benchmark source. Catch DCE, constant folding, missingBlackhole,@Fork(0),@Warmup < 5,finalconstants in the op, missing@State, raw loops without@OperationsPerInvocation. Most "fast" results come from broken benchmarks; catching this before running saves hours. - Identify the build system — Maven (
pom.xmlwithjmh-core) or Gradle (me.champeau.jmhplugin). Setup differs; running differs. Seereferences/maven.mdorreferences/gradle.md. - Pick the right
Mode—Throughputfor ops/sec,AverageTimefor ns/op,SingleShotTimefor cold-path / startup,SampleTimefor distribution (p50/p99). Wrong mode → wrong question answered. Seereferences/modes.md. - Write the benchmark — annotate class with
@State(Scope.Benchmark),@BenchmarkMode,@OutputTimeUnit,@Fork(value=3, jvmArgs={"-Xmx2g","-Xms2g"}),@Warmup(iterations=5),@Measurement(iterations=10). Every@Benchmarkmethod either returns a value or takes aBlackholeparameter. Use@Paramfor matrices instead of separate methods. - Re-check the source against the checklist after edits.
- Run with profilers attached — never run benchmarks without
-prof gc(allocation rate context) and ideally-prof async:output=flamegraph(flame graph per benchmark). Seereferences/profilers.md. - Output JSON (
-rf json -rff results.json) — never trust the console table alone; JSON is what diffing and visualization tools consume. - Analyze — drag
results.jsonto https://jmh.morethan.io for charts, or use Bencher/Codspeed in CI for continuous diff. Seereferences/analysis.md. - Report with confidence intervals — JMH prints
Score ± Error (99.9%). Two means are not different if their confidence intervals overlap. Don't claim "10% faster" inside the noise band.
Quick reference
# Maven — build + run a single benchmark class
mvn clean verify -DskipTests
java -jar target/benchmarks.jar MyBench -wi 10 -i 10 -f 3 -prof gc -rf json -rff result.json
# Gradle (me.champeau.jmh plugin) — run all benchmarks in jmh source set
./gradlew jmh
# Run only matching benchmarks (regex)
java -jar target/benchmarks.jar 'com\.acme\..*Hash.*'
# Profile per-benchmark with async-profiler
java -jar target/benchmarks.jar MyBench -prof async:output=flamegraph;dir=profiles
Common modes
| Mode | Unit | When |
|---|---|---|
Throughput | ops/time | "how many per second" — default for hot-path code |
AverageTime | time/op | "how long per call" — typical for latency-sensitive ops |
SampleTime | time/op (sampled) | distribution incl. p50/p95/p99 — outlier-aware |
SingleShotTime | time/op (one-shot, no warmup-loop) | cold start, init code, single-event measurement |
References
| File | When to read |
|---|---|
references/intro.md | Read first — what JMH is, why naive benchmarks lie, minimal example, how to read the score table + GC columns + percentiles, golden-default checklist |
references/pitfalls.md | Always before reviewing/writing a benchmark — 15 antipatterns (DCE, constant folding, false sharing, etc.) + 15-point pre-flight checklist + minimal correct template |
references/maven.md | Maven pom.xml setup, archetype, run command, multi-module projects |
references/gradle.md | me.champeau.jmh plugin config, jmh {} block, source set, IDE integration |
references/modes.md | Mode + State + Scope + @OperationsPerInvocation deep dive |
references/profilers.md | -prof gc, -prof async, -prof perfasm, -prof jfr, -prof stack — when to use which |
references/analysis.md | JSON schema, jmh.morethan.io, statistical interpretation, CI integration (Bencher, Codspeed) |
Output format
- Raw run: console table +
result.json(always emit JSON with-rf json -rff). - For sharing: upload JSON to https://jmh.morethan.io and share the URL.
- For CI: integrate with Bencher (
bencher run) or Codspeed (codspeed run); both have JMH adapters. - For deep analysis: pair with
async-profilerJFR per benchmark; render flame graphs withjfrconv.
What ships with it: 7 files
43.9 KB alongside SKILL.md
references/
- analysis.md5.2 KB
- gradle.md5.7 KB
- intro.md9.0 KB
- maven.md4.8 KB
- modes.md5.5 KB
- pitfalls.md8.4 KB
- profilers.md5.3 KB