jmh-basics: JMH tutorial module (dead-code elimination, Blackhole, constant folding, warmup/forks, state scopes, -prof gc)

Eight benchmark/demo classes, a deterministic test suite, and 12 captured output files
generated by scripts/run-all.sh on JDK 25.0.5 (Temurin) with JMH 1.37.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b6Fj2JNt7dRev4pZPe
This commit is contained in:
2026-11-08 18:31:11 +00:00
co-authored by Claude Sonnet 5.5
parent f6237d4117
commit db129fb1ce
27 changed files with 2018 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
# jmh-basics
Companion code for the ankurm.com post *"JMH Tutorial: Writing Java Microbenchmarks That Don't Lie."*
Module in `java-core-examples`, the Java-core series. There is deliberately no `docs/` folder: the
deeper material lives in collapsible sections inside the post itself.
## Versions this was built and tested against
| Component | Version | Notes |
|---|---|---|
| JDK | 25.0.5+8 (Temurin, LTS) | Compiler blackholes were auto-detected on this JVM; see `output/02`. |
| JMH | 1.37 | Latest GA on Maven Central; released 3 Aug 2023. |
| JUnit Jupiter | 5.11.0 | |
| Maven | 3.9.11 | |
| Host | 2 vCPU shared sandbox | Read the shape of the numbers, not the third significant digit. |
## Quickstart
```bash
export JAVA_HOME=/path/to/jdk-25
mvn -q package -DskipTests # builds target/benchmarks.jar (shaded, runnable)
java -cp target/classes com.ankurm.jmhbasics.NaiveTimingDemo
java -jar target/benchmarks.jar DeadCodeBenchmark
java -jar target/benchmarks.jar AllocationBenchmark -prof gc
```
`scripts/run-all.sh` regenerates every file in `output/` (about 8 minutes on 2 vCPUs).
`scripts/run.sh <ClassName>` runs one demo class ad hoc.
## What's in here
| File | What it shows |
|---|---|
| `NaiveTimingDemo.java` | The `System.nanoTime()` loop: a discarded result reports 0.000 ns/op, and the kept version changes from round to round while the JIT warms up. |
| `DeadCodeBenchmark.java` | Empty baseline, discarded result, returned result, `Blackhole`, and constant folding: `Math.pow` of a constant folds, `Math.log` of a constant does not, and a `final` instance field IS folded like a constant (an early draft of the Javadoc claimed the opposite; the measurement and JMHSample_10 both disagreed). |
| `LifecycleDemo.java` | Counts how often every `@Setup`/`@TearDown` level fires, by running JMH with `forks(0)` in-process. |
| `ForkingBenchmark.java` | One shared call site, two implementations, run with `-f 0` and `-f 1` to show type-profile pollution. |
| `StateScopeBenchmark.java` | `Scope.Thread` vs `Scope.Benchmark` with two threads. |
| `AllocationBenchmark.java` | Escape analysis: the same `new Point(..)` allocates 0 or 24 bytes per call depending on whether it escapes. Use `-prof gc`. |
| `SumBenchmark.java` | `@Param` over three input sizes, `@Setup` outside the measured region. |
| `SortBenchmark.java` | Warmup trace (`-wi 10 -w 200ms`) and three benchmark modes in one run. |
| `JmhBasicsTest.java` | Pins the deterministic claims: lifecycle counts, generated benchmark list, benchmark bodies. No timing assertions. |
| `output/01-12` | Captured runs of all of the above. |
## Notes worth knowing before reading the post
- **Each `@BenchmarkMode` value is a separate benchmark entry.** `SortBenchmark.sortCopy` carries three modes, so the generated `META-INF/BenchmarkList` has three lines for it (21 entries for 19 methods; the test pins this).
- **Run `LifecycleDemo` from the shaded jar**, not from `target/classes`: JMH is not on the plain classpath.
- **Timing figures drift.** Several error margins in `output/` are wide on this shared host. The post draws conclusions only from differences far larger than the error bars.
## License
MIT - see the [repo-wide LICENSE](../LICENSE).
+11
View File
@@ -0,0 +1,11 @@
Naive System.nanoTime() timing, 20000000 iterations per round
round discarded (ns/op) kept (ns/op)
1 0.688 9.170
2 0.457 8.751
3 0.000 7.994
4 0.000 7.583
5 0.000 7.502
6 0.000 7.419
checksum (ignore): 1.897349195729284E9
+343
View File
@@ -0,0 +1,343 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.baseline
# Run progress: 0.00% complete, ETA 00:02:08
# Fork: 1 of 2
# Warmup Iteration 1: 0.670 ns/op
# Warmup Iteration 2: 0.715 ns/op
# Warmup Iteration 3: 0.320 ns/op
Iteration 1: 0.327 ns/op
Iteration 2: 0.320 ns/op
Iteration 3: 0.386 ns/op
Iteration 4: 0.316 ns/op
Iteration 5: 0.320 ns/op
# Run progress: 6.25% complete, ETA 00:02:05
# Fork: 2 of 2
# Warmup Iteration 1: 0.732 ns/op
# Warmup Iteration 2: 0.719 ns/op
# Warmup Iteration 3: 0.509 ns/op
Iteration 1: 0.359 ns/op
Iteration 2: 0.344 ns/op
Iteration 3: 0.403 ns/op
Iteration 4: 0.327 ns/op
Iteration 5: 0.352 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.baseline":
0.345 ±(99.9%) 0.045 ns/op [Average]
(min, avg, max) = (0.316, 0.345, 0.403), stdev = 0.030
CI (99.9%): [0.300, 0.390] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.deadCode
# Run progress: 12.50% complete, ETA 00:01:56
# Fork: 1 of 2
# Warmup Iteration 1: 0.849 ns/op
# Warmup Iteration 2: 0.664 ns/op
# Warmup Iteration 3: 0.330 ns/op
Iteration 1: 0.364 ns/op
Iteration 2: 0.393 ns/op
Iteration 3: 0.318 ns/op
Iteration 4: 0.330 ns/op
Iteration 5: 0.392 ns/op
# Run progress: 18.75% complete, ETA 00:01:48
# Fork: 2 of 2
# Warmup Iteration 1: 0.719 ns/op
# Warmup Iteration 2: 0.703 ns/op
# Warmup Iteration 3: 0.366 ns/op
Iteration 1: 0.364 ns/op
Iteration 2: 0.371 ns/op
Iteration 3: 0.334 ns/op
Iteration 4: 0.367 ns/op
Iteration 5: 0.409 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.deadCode":
0.364 ±(99.9%) 0.045 ns/op [Average]
(min, avg, max) = (0.318, 0.364, 0.409), stdev = 0.030
CI (99.9%): [0.320, 0.409] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.logOfConstant
# Run progress: 25.00% complete, ETA 00:01:39
# Fork: 1 of 2
# Warmup Iteration 1: 8.047 ns/op
# Warmup Iteration 2: 7.451 ns/op
# Warmup Iteration 3: 6.837 ns/op
Iteration 1: 6.602 ns/op
Iteration 2: 6.579 ns/op
Iteration 3: 7.626 ns/op
Iteration 4: 6.919 ns/op
Iteration 5: 7.168 ns/op
# Run progress: 31.25% complete, ETA 00:01:31
# Fork: 2 of 2
# Warmup Iteration 1: 7.435 ns/op
# Warmup Iteration 2: 7.437 ns/op
# Warmup Iteration 3: 6.543 ns/op
Iteration 1: 6.941 ns/op
Iteration 2: 6.895 ns/op
Iteration 3: 6.854 ns/op
Iteration 4: 8.124 ns/op
Iteration 5: 6.648 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.logOfConstant":
7.036 ±(99.9%) 0.743 ns/op [Average]
(min, avg, max) = (6.579, 7.036, 8.124), stdev = 0.491
CI (99.9%): [6.293, 7.779] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.powOfConstant
# Run progress: 37.50% complete, ETA 00:01:22
# Fork: 1 of 2
# Warmup Iteration 1: 0.345 ns/op
# Warmup Iteration 2: 0.408 ns/op
# Warmup Iteration 3: 0.432 ns/op
Iteration 1: 0.346 ns/op
Iteration 2: 0.341 ns/op
Iteration 3: 0.333 ns/op
Iteration 4: 0.481 ns/op
Iteration 5: 0.561 ns/op
# Run progress: 43.75% complete, ETA 00:01:14
# Fork: 2 of 2
# Warmup Iteration 1: 0.359 ns/op
# Warmup Iteration 2: 0.425 ns/op
# Warmup Iteration 3: 0.340 ns/op
Iteration 1: 0.428 ns/op
Iteration 2: 0.349 ns/op
Iteration 3: 0.497 ns/op
Iteration 4: 0.576 ns/op
Iteration 5: 0.494 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.powOfConstant":
0.441 ±(99.9%) 0.142 ns/op [Average]
(min, avg, max) = (0.333, 0.441, 0.576), stdev = 0.094
CI (99.9%): [0.298, 0.583] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.powOfField
# Run progress: 50.00% complete, ETA 00:01:06
# Fork: 1 of 2
# Warmup Iteration 1: 14.437 ns/op
# Warmup Iteration 2: 15.012 ns/op
# Warmup Iteration 3: 13.839 ns/op
Iteration 1: 14.298 ns/op
Iteration 2: 14.261 ns/op
Iteration 3: 13.866 ns/op
Iteration 4: 14.114 ns/op
Iteration 5: 13.812 ns/op
# Run progress: 56.25% complete, ETA 00:00:57
# Fork: 2 of 2
# Warmup Iteration 1: 14.805 ns/op
# Warmup Iteration 2: 15.195 ns/op
# Warmup Iteration 3: 13.683 ns/op
Iteration 1: 13.712 ns/op
Iteration 2: 15.469 ns/op
Iteration 3: 16.961 ns/op
Iteration 4: 13.916 ns/op
Iteration 5: 14.715 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.powOfField":
14.512 ±(99.9%) 1.522 ns/op [Average]
(min, avg, max) = (13.712, 14.512, 16.961), stdev = 1.007
CI (99.9%): [12.990, 16.035] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.powOfFinalField
# Run progress: 62.50% complete, ETA 00:00:49
# Fork: 1 of 2
# Warmup Iteration 1: 0.396 ns/op
# Warmup Iteration 2: 0.385 ns/op
# Warmup Iteration 3: 0.325 ns/op
Iteration 1: 0.603 ns/op
Iteration 2: 0.332 ns/op
Iteration 3: 0.327 ns/op
Iteration 4: 0.333 ns/op
Iteration 5: 0.359 ns/op
# Run progress: 68.75% complete, ETA 00:00:41
# Fork: 2 of 2
# Warmup Iteration 1: 0.419 ns/op
# Warmup Iteration 2: 0.366 ns/op
# Warmup Iteration 3: 0.342 ns/op
Iteration 1: 0.344 ns/op
Iteration 2: 0.338 ns/op
Iteration 3: 0.344 ns/op
Iteration 4: 0.352 ns/op
Iteration 5: 0.343 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.powOfFinalField":
0.367 ±(99.9%) 0.126 ns/op [Average]
(min, avg, max) = (0.327, 0.367, 0.603), stdev = 0.083
CI (99.9%): [0.242, 0.493] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.returned
# Run progress: 75.00% complete, ETA 00:00:33
# Fork: 1 of 2
# Warmup Iteration 1: 8.614 ns/op
# Warmup Iteration 2: 7.575 ns/op
# Warmup Iteration 3: 7.764 ns/op
Iteration 1: 7.586 ns/op
Iteration 2: 9.793 ns/op
Iteration 3: 8.194 ns/op
Iteration 4: 6.814 ns/op
Iteration 5: 6.702 ns/op
# Run progress: 81.25% complete, ETA 00:00:24
# Fork: 2 of 2
# Warmup Iteration 1: 7.652 ns/op
# Warmup Iteration 2: 6.920 ns/op
# Warmup Iteration 3: 7.625 ns/op
Iteration 1: 7.713 ns/op
Iteration 2: 6.881 ns/op
Iteration 3: 8.208 ns/op
Iteration 4: 6.475 ns/op
Iteration 5: 8.223 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.returned":
7.659 ±(99.9%) 1.521 ns/op [Average]
(min, avg, max) = (6.475, 7.659, 9.793), stdev = 1.006
CI (99.9%): [6.138, 9.180] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.DeadCodeBenchmark.viaBlackhole
# Run progress: 87.50% complete, ETA 00:00:16
# Fork: 1 of 2
# Warmup Iteration 1: 8.460 ns/op
# Warmup Iteration 2: 6.864 ns/op
# Warmup Iteration 3: 7.808 ns/op
Iteration 1: 8.439 ns/op
Iteration 2: 10.136 ns/op
Iteration 3: 9.039 ns/op
Iteration 4: 8.146 ns/op
Iteration 5: 7.843 ns/op
# Run progress: 93.75% complete, ETA 00:00:08
# Fork: 2 of 2
# Warmup Iteration 1: 6.960 ns/op
# Warmup Iteration 2: 7.100 ns/op
# Warmup Iteration 3: 7.036 ns/op
Iteration 1: 9.381 ns/op
Iteration 2: 7.383 ns/op
Iteration 3: 6.564 ns/op
Iteration 4: 6.919 ns/op
Iteration 5: 7.026 ns/op
Result "com.ankurm.jmhbasics.DeadCodeBenchmark.viaBlackhole":
8.088 ±(99.9%) 1.766 ns/op [Average]
(min, avg, max) = (6.564, 8.088, 10.136), stdev = 1.168
CI (99.9%): [6.322, 9.853] (assumes normal distribution)
# Run complete. Total time: 00:02:12
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
DeadCodeBenchmark.baseline avgt 10 0.345 ± 0.045 ns/op
DeadCodeBenchmark.deadCode avgt 10 0.364 ± 0.045 ns/op
DeadCodeBenchmark.logOfConstant avgt 10 7.036 ± 0.743 ns/op
DeadCodeBenchmark.powOfConstant avgt 10 0.441 ± 0.142 ns/op
DeadCodeBenchmark.powOfField avgt 10 14.512 ± 1.522 ns/op
DeadCodeBenchmark.powOfFinalField avgt 10 0.367 ± 0.126 ns/op
DeadCodeBenchmark.returned avgt 10 7.659 ± 1.521 ns/op
DeadCodeBenchmark.viaBlackhole avgt 10 8.088 ± 1.766 ns/op
+9
View File
@@ -0,0 +1,9 @@
Setup/TearDown counts for 1 warmup + 2 measurement iterations:
@Setup(Level.Trial) : 1
@Setup(Level.Iteration) : 3
@Setup(Level.Invocation) : 1039152 (one per benchmark call)
benchmark method calls : 1039152
@TearDown(Level.Invocation) : 1039152
@TearDown(Level.Iteration) : 3
@TearDown(Level.Trial) : 1
+117
View File
@@ -0,0 +1,117 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 8 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.ForkingBenchmark.measure_1_c1
# Run progress: 0.00% complete, ETA 00:00:33
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: 1.169 ns/op
# Warmup Iteration 2: 0.972 ns/op
# Warmup Iteration 3: 0.891 ns/op
Iteration 1: 0.797 ns/op
Iteration 2: 1.058 ns/op
Iteration 3: 0.842 ns/op
Iteration 4: 0.892 ns/op
Iteration 5: 0.870 ns/op
Iteration 6: 1.155 ns/op
Iteration 7: 0.884 ns/op
Iteration 8: 0.800 ns/op
Result "com.ankurm.jmhbasics.ForkingBenchmark.measure_1_c1":
0.912 ±(99.9%) 0.244 ns/op [Average]
(min, avg, max) = (0.797, 0.912, 1.155), stdev = 0.128
CI (99.9%): [0.668, 1.156] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 8 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.ForkingBenchmark.measure_2_c2
# Run progress: 33.33% complete, ETA 00:00:22
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: 1.287 ns/op
# Warmup Iteration 2: 1.593 ns/op
# Warmup Iteration 3: 2.833 ns/op
Iteration 1: 3.000 ns/op
Iteration 2: 2.695 ns/op
Iteration 3: 2.754 ns/op
Iteration 4: 2.416 ns/op
Iteration 5: 2.173 ns/op
Iteration 6: 2.287 ns/op
Iteration 7: 2.217 ns/op
Iteration 8: 2.100 ns/op
Result "com.ankurm.jmhbasics.ForkingBenchmark.measure_2_c2":
2.455 ±(99.9%) 0.620 ns/op [Average]
(min, avg, max) = (2.100, 2.455, 3.000), stdev = 0.324
CI (99.9%): [1.835, 3.075] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 8 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.ForkingBenchmark.measure_3_c1
# Run progress: 66.67% complete, ETA 00:00:11
# Fork: N/A, test runs in the host VM
# *** WARNING: Non-forked runs may silently omit JVM options, mess up profilers, disable compiler hints, etc. ***
# *** WARNING: Use non-forked runs only for debugging purposes, not for actual performance runs. ***
# Warmup Iteration 1: 2.188 ns/op
# Warmup Iteration 2: 2.224 ns/op
# Warmup Iteration 3: 2.666 ns/op
Iteration 1: 2.274 ns/op
Iteration 2: 2.184 ns/op
Iteration 3: 2.297 ns/op
Iteration 4: 2.201 ns/op
Iteration 5: 2.344 ns/op
Iteration 6: 2.517 ns/op
Iteration 7: 2.654 ns/op
Iteration 8: 2.302 ns/op
Result "com.ankurm.jmhbasics.ForkingBenchmark.measure_3_c1":
2.347 ±(99.9%) 0.307 ns/op [Average]
(min, avg, max) = (2.184, 2.347, 2.654), stdev = 0.161
CI (99.9%): [2.040, 2.654] (assumes normal distribution)
# Run complete. Total time: 00:00:33
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
ForkingBenchmark.measure_1_c1 avgt 8 0.912 ± 0.244 ns/op
ForkingBenchmark.measure_2_c2 avgt 8 2.455 ± 0.620 ns/op
ForkingBenchmark.measure_3_c1 avgt 8 2.347 ± 0.307 ns/op
+114
View File
@@ -0,0 +1,114 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 8 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.ForkingBenchmark.measure_1_c1
# Run progress: 0.00% complete, ETA 00:00:33
# Fork: 1 of 1
# Warmup Iteration 1: 0.752 ns/op
# Warmup Iteration 2: 0.687 ns/op
# Warmup Iteration 3: 0.725 ns/op
Iteration 1: 0.727 ns/op
Iteration 2: 0.793 ns/op
Iteration 3: 0.743 ns/op
Iteration 4: 0.716 ns/op
Iteration 5: 0.728 ns/op
Iteration 6: 0.800 ns/op
Iteration 7: 0.657 ns/op
Iteration 8: 0.658 ns/op
Result "com.ankurm.jmhbasics.ForkingBenchmark.measure_1_c1":
0.728 ±(99.9%) 0.101 ns/op [Average]
(min, avg, max) = (0.657, 0.728, 0.800), stdev = 0.053
CI (99.9%): [0.626, 0.829] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 8 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.ForkingBenchmark.measure_2_c2
# Run progress: 33.33% complete, ETA 00:00:22
# Fork: 1 of 1
# Warmup Iteration 1: 0.696 ns/op
# Warmup Iteration 2: 0.717 ns/op
# Warmup Iteration 3: 0.846 ns/op
Iteration 1: 0.863 ns/op
Iteration 2: 1.101 ns/op
Iteration 3: 0.780 ns/op
Iteration 4: 0.681 ns/op
Iteration 5: 0.735 ns/op
Iteration 6: 0.838 ns/op
Iteration 7: 0.683 ns/op
Iteration 8: 0.656 ns/op
Result "com.ankurm.jmhbasics.ForkingBenchmark.measure_2_c2":
0.792 ±(99.9%) 0.279 ns/op [Average]
(min, avg, max) = (0.656, 0.792, 1.101), stdev = 0.146
CI (99.9%): [0.513, 1.071] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 8 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.ForkingBenchmark.measure_3_c1
# Run progress: 66.67% complete, ETA 00:00:11
# Fork: 1 of 1
# Warmup Iteration 1: 0.713 ns/op
# Warmup Iteration 2: 0.713 ns/op
# Warmup Iteration 3: 0.640 ns/op
Iteration 1: 0.652 ns/op
Iteration 2: 0.655 ns/op
Iteration 3: 0.693 ns/op
Iteration 4: 0.835 ns/op
Iteration 5: 0.822 ns/op
Iteration 6: 0.739 ns/op
Iteration 7: 0.784 ns/op
Iteration 8: 0.686 ns/op
Result "com.ankurm.jmhbasics.ForkingBenchmark.measure_3_c1":
0.733 ±(99.9%) 0.140 ns/op [Average]
(min, avg, max) = (0.652, 0.733, 0.835), stdev = 0.073
CI (99.9%): [0.593, 0.873] (assumes normal distribution)
# Run complete. Total time: 00:00:33
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
ForkingBenchmark.measure_1_c1 avgt 8 0.728 ± 0.101 ns/op
ForkingBenchmark.measure_2_c2 avgt 8 0.792 ± 0.279 ns/op
ForkingBenchmark.measure_3_c1 avgt 8 0.733 ± 0.140 ns/op
+97
View File
@@ -0,0 +1,97 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 2 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.StateScopeBenchmark.perThreadCounter
# Run progress: 0.00% complete, ETA 00:00:32
# Fork: 1 of 2
# Warmup Iteration 1: 7.678 ns/op
# Warmup Iteration 2: 8.112 ns/op
# Warmup Iteration 3: 7.597 ns/op
Iteration 1: 7.543 ns/op
Iteration 2: 7.587 ns/op
Iteration 3: 7.824 ns/op
Iteration 4: 8.115 ns/op
Iteration 5: 8.116 ns/op
# Run progress: 25.00% complete, ETA 00:00:24
# Fork: 2 of 2
# Warmup Iteration 1: 7.846 ns/op
# Warmup Iteration 2: 7.687 ns/op
# Warmup Iteration 3: 8.156 ns/op
Iteration 1: 7.978 ns/op
Iteration 2: 8.031 ns/op
Iteration 3: 7.915 ns/op
Iteration 4: 8.456 ns/op
Iteration 5: 7.931 ns/op
Result "com.ankurm.jmhbasics.StateScopeBenchmark.perThreadCounter":
7.950 ±(99.9%) 0.401 ns/op [Average]
(min, avg, max) = (7.543, 7.950, 8.456), stdev = 0.265
CI (99.9%): [7.549, 8.351] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 2 threads, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.StateScopeBenchmark.sharedCounter
# Run progress: 50.00% complete, ETA 00:00:16
# Fork: 1 of 2
# Warmup Iteration 1: 28.549 ns/op
# Warmup Iteration 2: 35.212 ns/op
# Warmup Iteration 3: 35.105 ns/op
Iteration 1: 32.801 ns/op
Iteration 2: 35.661 ns/op
Iteration 3: 33.767 ns/op
Iteration 4: 32.006 ns/op
Iteration 5: 35.276 ns/op
# Run progress: 75.00% complete, ETA 00:00:08
# Fork: 2 of 2
# Warmup Iteration 1: 33.768 ns/op
# Warmup Iteration 2: 35.397 ns/op
# Warmup Iteration 3: 37.498 ns/op
Iteration 1: 34.394 ns/op
Iteration 2: 33.455 ns/op
Iteration 3: 28.463 ns/op
Iteration 4: 15.008 ns/op
Iteration 5: 35.151 ns/op
Result "com.ankurm.jmhbasics.StateScopeBenchmark.sharedCounter":
31.598 ±(99.9%) 9.365 ns/op [Average]
(min, avg, max) = (15.008, 31.598, 35.661), stdev = 6.194
CI (99.9%): [22.233, 40.963] (assumes normal distribution)
# Run complete. Total time: 00:00:33
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
StateScopeBenchmark.perThreadCounter avgt 10 7.950 ± 0.401 ns/op
StateScopeBenchmark.sharedCounter avgt 10 31.598 ± 9.365 ns/op
+203
View File
@@ -0,0 +1,203 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.AllocationBenchmark.arrayEscapes
# Run progress: 0.00% complete, ETA 00:00:18
# Fork: 1 of 1
# Warmup Iteration 1: 7.135 ns/op
# Warmup Iteration 2: 5.665 ns/op
# Warmup Iteration 3: 5.072 ns/op
Iteration 1: 5.345 ns/op
gc.alloc.rate: 5707.275 MB/sec
gc.alloc.rate.norm: 32.000 B/op
gc.count: 18.000 counts
gc.time: 21.000 ms
Iteration 2: 5.649 ns/op
gc.alloc.rate: 5400.357 MB/sec
gc.alloc.rate.norm: 32.000 B/op
gc.count: 18.000 counts
gc.time: 22.000 ms
Iteration 3: 5.596 ns/op
gc.alloc.rate: 5448.664 MB/sec
gc.alloc.rate.norm: 32.000 B/op
gc.count: 17.000 counts
gc.time: 22.000 ms
Result "com.ankurm.jmhbasics.AllocationBenchmark.arrayEscapes":
5.530 ±(99.9%) 2.966 ns/op [Average]
(min, avg, max) = (5.345, 5.530, 5.649), stdev = 0.163
CI (99.9%): [2.564, 8.496] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.arrayEscapes:gc.alloc.rate":
5518.765 ±(99.9%) 3010.780 MB/sec [Average]
(min, avg, max) = (5400.357, 5518.765, 5707.275), stdev = 165.031
CI (99.9%): [2507.986, 8529.545] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.arrayEscapes:gc.alloc.rate.norm":
32.000 ±(99.9%) 0.001 B/op [Average]
(min, avg, max) = (32.000, 32.000, 32.000), stdev = 0.001
CI (99.9%): [32.000, 32.000] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.arrayEscapes:gc.count":
53.000 ±(99.9%) 0.001 counts [Sum]
(min, avg, max) = (17.000, 17.667, 18.000), stdev = 0.577
CI (99.9%): [53.000, 53.000] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.arrayEscapes:gc.time":
65.000 ±(99.9%) 0.001 ms [Sum]
(min, avg, max) = (21.000, 21.667, 22.000), stdev = 0.577
CI (99.9%): [65.000, 65.000] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.AllocationBenchmark.doesNotEscape
# Run progress: 33.33% complete, ETA 00:00:12
# Fork: 1 of 1
# Warmup Iteration 1: 0.664 ns/op
# Warmup Iteration 2: 0.732 ns/op
# Warmup Iteration 3: 0.754 ns/op
Iteration 1: 1.044 ns/op
gc.alloc.rate: 0.007 MB/sec
gc.alloc.rate.norm: ≈ 10⁻⁵ B/op
gc.count: ≈ 0 counts
Iteration 2: 0.849 ns/op
gc.alloc.rate: 0.007 MB/sec
gc.alloc.rate.norm: ≈ 10⁻⁵ B/op
gc.count: ≈ 0 counts
Iteration 3: 0.774 ns/op
gc.alloc.rate: 0.007 MB/sec
gc.alloc.rate.norm: ≈ 10⁻⁵ B/op
gc.count: ≈ 0 counts
Result "com.ankurm.jmhbasics.AllocationBenchmark.doesNotEscape":
0.889 ±(99.9%) 2.544 ns/op [Average]
(min, avg, max) = (0.774, 0.889, 1.044), stdev = 0.139
CI (99.9%): [≈ 0, 3.432] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.doesNotEscape:gc.alloc.rate":
0.007 ±(99.9%) 0.001 MB/sec [Average]
(min, avg, max) = (0.007, 0.007, 0.007), stdev = 0.001
CI (99.9%): [0.007, 0.007] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.doesNotEscape:gc.alloc.rate.norm":
≈ 10⁻⁵ B/op
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.doesNotEscape:gc.count":
≈ 0 counts
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 3 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.AllocationBenchmark.escapes
# Run progress: 66.67% complete, ETA 00:00:06
# Fork: 1 of 1
# Warmup Iteration 1: 6.088 ns/op
# Warmup Iteration 2: 4.657 ns/op
# Warmup Iteration 3: 4.507 ns/op
Iteration 1: 4.959 ns/op
gc.alloc.rate: 4609.827 MB/sec
gc.alloc.rate.norm: 24.000 B/op
gc.count: 16.000 counts
gc.time: 20.000 ms
Iteration 2: 4.719 ns/op
gc.alloc.rate: 4843.713 MB/sec
gc.alloc.rate.norm: 24.000 B/op
gc.count: 17.000 counts
gc.time: 21.000 ms
Iteration 3: 4.898 ns/op
gc.alloc.rate: 4671.068 MB/sec
gc.alloc.rate.norm: 24.000 B/op
gc.count: 16.000 counts
gc.time: 18.000 ms
Result "com.ankurm.jmhbasics.AllocationBenchmark.escapes":
4.859 ±(99.9%) 2.271 ns/op [Average]
(min, avg, max) = (4.719, 4.859, 4.959), stdev = 0.124
CI (99.9%): [2.588, 7.130] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.escapes:gc.alloc.rate":
4708.203 ±(99.9%) 2212.682 MB/sec [Average]
(min, avg, max) = (4609.827, 4708.203, 4843.713), stdev = 121.285
CI (99.9%): [2495.521, 6920.885] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.escapes:gc.alloc.rate.norm":
24.000 ±(99.9%) 0.001 B/op [Average]
(min, avg, max) = (24.000, 24.000, 24.000), stdev = 0.001
CI (99.9%): [24.000, 24.000] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.escapes:gc.count":
49.000 ±(99.9%) 0.001 counts [Sum]
(min, avg, max) = (16.000, 16.333, 17.000), stdev = 0.577
CI (99.9%): [49.000, 49.000] (assumes normal distribution)
Secondary result "com.ankurm.jmhbasics.AllocationBenchmark.escapes:gc.time":
59.000 ±(99.9%) 0.001 ms [Sum]
(min, avg, max) = (18.000, 19.667, 21.000), stdev = 1.528
CI (99.9%): [59.000, 59.000] (assumes normal distribution)
# Run complete. Total time: 00:00:19
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
AllocationBenchmark.arrayEscapes avgt 3 5.530 ± 2.966 ns/op
AllocationBenchmark.arrayEscapes:gc.alloc.rate avgt 3 5518.765 ± 3010.780 MB/sec
AllocationBenchmark.arrayEscapes:gc.alloc.rate.norm avgt 3 32.000 ± 0.001 B/op
AllocationBenchmark.arrayEscapes:gc.count avgt 3 53.000 counts
AllocationBenchmark.arrayEscapes:gc.time avgt 3 65.000 ms
AllocationBenchmark.doesNotEscape avgt 3 0.889 ± 2.544 ns/op
AllocationBenchmark.doesNotEscape:gc.alloc.rate avgt 3 0.007 ± 0.001 MB/sec
AllocationBenchmark.doesNotEscape:gc.alloc.rate.norm avgt 3 ≈ 10⁻⁵ B/op
AllocationBenchmark.doesNotEscape:gc.count avgt 3 ≈ 0 counts
AllocationBenchmark.escapes avgt 3 4.859 ± 2.271 ns/op
AllocationBenchmark.escapes:gc.alloc.rate avgt 3 4708.203 ± 2212.682 MB/sec
AllocationBenchmark.escapes:gc.alloc.rate.norm avgt 3 24.000 ± 0.001 B/op
AllocationBenchmark.escapes:gc.count avgt 3 49.000 counts
AllocationBenchmark.escapes:gc.time avgt 3 59.000 ms
+108
View File
@@ -0,0 +1,108 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.SumBenchmark.sum
# Parameters: (size = 10)
# Run progress: 0.00% complete, ETA 00:00:24
# Fork: 1 of 1
# Warmup Iteration 1: 11.152 ns/op
# Warmup Iteration 2: 9.349 ns/op
# Warmup Iteration 3: 11.239 ns/op
Iteration 1: 11.426 ns/op
Iteration 2: 11.575 ns/op
Iteration 3: 11.528 ns/op
Iteration 4: 8.986 ns/op
Iteration 5: 10.091 ns/op
Result "com.ankurm.jmhbasics.SumBenchmark.sum":
10.721 ±(99.9%) 4.427 ns/op [Average]
(min, avg, max) = (8.986, 10.721, 11.575), stdev = 1.150
CI (99.9%): [6.294, 15.149] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.SumBenchmark.sum
# Parameters: (size = 1000)
# Run progress: 33.33% complete, ETA 00:00:16
# Fork: 1 of 1
# Warmup Iteration 1: 72.906 ns/op
# Warmup Iteration 2: 61.956 ns/op
# Warmup Iteration 3: 60.801 ns/op
Iteration 1: 53.346 ns/op
Iteration 2: 57.255 ns/op
Iteration 3: 52.866 ns/op
Iteration 4: 54.738 ns/op
Iteration 5: 54.117 ns/op
Result "com.ankurm.jmhbasics.SumBenchmark.sum":
54.465 ±(99.9%) 6.611 ns/op [Average]
(min, avg, max) = (52.866, 54.465, 57.255), stdev = 1.717
CI (99.9%): [47.854, 61.075] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.SumBenchmark.sum
# Parameters: (size = 100000)
# Run progress: 66.67% complete, ETA 00:00:08
# Fork: 1 of 1
# Warmup Iteration 1: 8474.836 ns/op
# Warmup Iteration 2: 7431.944 ns/op
# Warmup Iteration 3: 7347.101 ns/op
Iteration 1: 7377.289 ns/op
Iteration 2: 7499.047 ns/op
Iteration 3: 7046.908 ns/op
Iteration 4: 7454.035 ns/op
Iteration 5: 7662.632 ns/op
Result "com.ankurm.jmhbasics.SumBenchmark.sum":
7407.982 ±(99.9%) 875.072 ns/op [Average]
(min, avg, max) = (7046.908, 7407.982, 7662.632), stdev = 227.254
CI (99.9%): [6532.910, 8283.055] (assumes normal distribution)
# Run complete. Total time: 00:00:25
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark (size) Mode Cnt Score Error Units
SumBenchmark.sum 10 avgt 5 10.721 ± 4.427 ns/op
SumBenchmark.sum 1000 avgt 5 54.465 ± 6.611 ns/op
SumBenchmark.sum 100000 avgt 5 7407.982 ± 875.072 ns/op
+52
View File
@@ -0,0 +1,52 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 10 iterations, 200 ms each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.SortBenchmark.sortCopy
# Run progress: 0.00% complete, ETA 00:00:07
# Fork: 1 of 1
# Warmup Iteration 1: 9.502 us/op
# Warmup Iteration 2: 7.662 us/op
# Warmup Iteration 3: 4.997 us/op
# Warmup Iteration 4: 4.586 us/op
# Warmup Iteration 5: 4.362 us/op
# Warmup Iteration 6: 6.260 us/op
# Warmup Iteration 7: 6.648 us/op
# Warmup Iteration 8: 5.701 us/op
# Warmup Iteration 9: 4.546 us/op
# Warmup Iteration 10: 4.359 us/op
Iteration 1: 5.683 us/op
Iteration 2: 6.121 us/op
Iteration 3: 4.753 us/op
Iteration 4: 4.963 us/op
Iteration 5: 5.553 us/op
Result "com.ankurm.jmhbasics.SortBenchmark.sortCopy":
5.414 ±(99.9%) 2.138 us/op [Average]
(min, avg, max) = (4.753, 5.414, 6.121), stdev = 0.555
CI (99.9%): [3.277, 7.552] (assumes normal distribution)
# Run complete. Total time: 00:00:07
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
SortBenchmark.sortCopy avgt 5 5.414 ± 2.138 us/op
+186
View File
@@ -0,0 +1,186 @@
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: com.ankurm.jmhbasics.SortBenchmark.sortCopy
# Run progress: 0.00% complete, ETA 00:00:24
# Fork: 1 of 1
# Warmup Iteration 1: 0.167 ops/us
# Warmup Iteration 2: 0.198 ops/us
# Warmup Iteration 3: 0.199 ops/us
Iteration 1: 0.186 ops/us
Iteration 2: 0.200 ops/us
Iteration 3: 0.185 ops/us
Iteration 4: 0.156 ops/us
Iteration 5: 0.174 ops/us
Result "com.ankurm.jmhbasics.SortBenchmark.sortCopy":
0.180 ±(99.9%) 0.063 ops/us [Average]
(min, avg, max) = (0.156, 0.180, 0.200), stdev = 0.016
CI (99.9%): [0.117, 0.243] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Average time, time/op
# Benchmark: com.ankurm.jmhbasics.SortBenchmark.sortCopy
# Run progress: 33.33% complete, ETA 00:00:16
# Fork: 1 of 1
# Warmup Iteration 1: 6.346 us/op
# Warmup Iteration 2: 5.192 us/op
# Warmup Iteration 3: 5.520 us/op
Iteration 1: 5.367 us/op
Iteration 2: 5.330 us/op
Iteration 3: 5.343 us/op
Iteration 4: 6.901 us/op
Iteration 5: 5.716 us/op
Result "com.ankurm.jmhbasics.SortBenchmark.sortCopy":
5.731 ±(99.9%) 2.592 us/op [Average]
(min, avg, max) = (5.330, 5.731, 6.901), stdev = 0.673
CI (99.9%): [3.140, 8.323] (assumes normal distribution)
# JMH version: 1.37
# VM version: JDK 25.0.5, OpenJDK 64-Bit Server VM, 25.0.5+8-LTS
# VM invoker: /tmp/tools/jdk-25.0.5+8/bin/java
# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable)
# Warmup: 3 iterations, 1 s each
# Measurement: 5 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Sampling time
# Benchmark: com.ankurm.jmhbasics.SortBenchmark.sortCopy
# Run progress: 66.67% complete, ETA 00:00:08
# Fork: 1 of 1
# Warmup Iteration 1: 9.133 ±(99.9%) 1.896 us/op
# Warmup Iteration 2: 7.146 ±(99.9%) 0.975 us/op
# Warmup Iteration 3: 6.063 ±(99.9%) 0.366 us/op
Iteration 1: 8.042 ±(99.9%) 1.788 us/op
p0.00: 3.428 us/op
p0.50: 5.960 us/op
p0.90: 6.808 us/op
p0.95: 7.432 us/op
p0.99: 28.379 us/op
p0.999: 65.180 us/op
p0.9999: 4063.431 us/op
p1.00: 4067.328 us/op
Iteration 2: 6.504 ±(99.9%) 1.880 us/op
p0.00: 3.420 us/op
p0.50: 4.480 us/op
p0.90: 6.352 us/op
p0.95: 6.744 us/op
p0.99: 25.392 us/op
p0.999: 51.584 us/op
p0.9999: 4057.682 us/op
p1.00: 11911.168 us/op
Iteration 3: 5.223 ±(99.9%) 0.336 us/op
p0.00: 3.424 us/op
p0.50: 4.280 us/op
p0.90: 5.776 us/op
p0.95: 6.272 us/op
p0.99: 20.843 us/op
p0.999: 62.631 us/op
p0.9999: 953.847 us/op
p1.00: 1581.056 us/op
Iteration 4: 7.875 ±(99.9%) 1.578 us/op
p0.00: 3.428 us/op
p0.50: 6.104 us/op
p0.90: 6.808 us/op
p0.95: 7.906 us/op
p0.99: 35.626 us/op
p0.999: 195.147 us/op
p0.9999: 4194.614 us/op
p1.00: 8192.000 us/op
Iteration 5: 5.648 ±(99.9%) 0.311 us/op
p0.00: 3.416 us/op
p0.50: 4.504 us/op
p0.90: 6.416 us/op
p0.95: 6.856 us/op
p0.99: 26.271 us/op
p0.999: 69.818 us/op
p0.9999: 362.418 us/op
p1.00: 2050.048 us/op
Result "com.ankurm.jmhbasics.SortBenchmark.sortCopy":
N = 112693
mean = 6.532 ±(99.9%) 0.580 us/op
Histogram, us/op:
[ 0.000, 1250.000) = 112674
[ 1250.000, 2500.000) = 8
[ 2500.000, 3750.000) = 0
[ 3750.000, 5000.000) = 9
[ 5000.000, 6250.000) = 0
[ 6250.000, 7500.000) = 0
[ 7500.000, 8750.000) = 1
[ 8750.000, 10000.000) = 0
[10000.000, 11250.000) = 0
[11250.000, 12500.000) = 1
[12500.000, 13750.000) = 0
[13750.000, 15000.000) = 0
[15000.000, 16250.000) = 0
[16250.000, 17500.000) = 0
[17500.000, 18750.000) = 0
Percentiles, us/op:
p(0.0000) = 3.416 us/op
p(50.0000) = 5.352 us/op
p(90.0000) = 6.544 us/op
p(95.0000) = 7.024 us/op
p(99.0000) = 26.944 us/op
p(99.9000) = 91.151 us/op
p(99.9900) = 3620.126 us/op
p(99.9990) = 11439.057 us/op
p(99.9999) = 11911.168 us/op
p(100.0000) = 11911.168 us/op
# Run complete. Total time: 00:00:25
REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on
why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial
experiments, perform baseline and negative tests that provide experimental control, make sure
the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts.
Do not assume the numbers tell you what you want them to tell.
NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise
extra caution when trusting the results, look into the generated code to check the benchmark still
works, and factor in a small probability of new VM bugs. Additionally, while comparisons between
different JVMs are already problematic, the performance difference caused by different Blackhole
modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons.
Benchmark Mode Cnt Score Error Units
SortBenchmark.sortCopy thrpt 5 0.180 ± 0.063 ops/us
SortBenchmark.sortCopy avgt 5 5.731 ± 2.592 us/op
SortBenchmark.sortCopy sample 112693 6.532 ± 0.580 us/op
SortBenchmark.sortCopy:p0.00 sample 3.416 us/op
SortBenchmark.sortCopy:p0.50 sample 5.352 us/op
SortBenchmark.sortCopy:p0.90 sample 6.544 us/op
SortBenchmark.sortCopy:p0.95 sample 7.024 us/op
SortBenchmark.sortCopy:p0.99 sample 26.944 us/op
SortBenchmark.sortCopy:p0.999 sample 91.151 us/op
SortBenchmark.sortCopy:p0.9999 sample 3620.126 us/op
SortBenchmark.sortCopy:p1.00 sample 11911.168 us/op
+28
View File
@@ -0,0 +1,28 @@
Supported profilers:
cl: Classloader profiling via standard MBeans
comp: JIT compiler profiling via standard MBeans
gc: GC profiling via standard MBeans
jfr: Java Flight Recorder profiler
mempool: Memory pool/footprint profiling via standard MBeans
pauses: Pauses profiler
safepoints: Safepoints profiler
stack: Simple and naive Java stack profiler
Unsupported profilers:
async: <none>
Unable to load async-profiler. Ensure asyncProfiler library is on LD_LIBRARY_PATH (Linux), DYLD_LIBRARY_PATH (Mac OS), or -Djava.library.path. Alternatively, point to explicit library location with -prof async:libPath=<path>.
dtraceasm: <none>
[sudo: dtrace: command not found
]
perf: <none>
[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ]
perfasm: <none>
[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ]
perfc2c: <none>
[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ]
perfnorm: <none>
[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ]
xperfasm: <none>
Unable to start the profiler. Try running JMH as Administrator, and ensure that previous profiling session is stopped. Use 'xperf -stop' to stop the active profiling session.: [Cannot run program "xperf": Exec failed, error: 2 (No such file or directory) ]
+4
View File
@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------
Test set: com.ankurm.jmhbasics.JmhBasicsTest
-------------------------------------------------------------------------------
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.314 s -- in com.ankurm.jmhbasics.JmhBasicsTest
+83
View File
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.ankurm</groupId>
<artifactId>java-core-examples</artifactId>
<version>1.0</version>
</parent>
<artifactId>jmh-basics</artifactId>
<name>jmh-basics</name>
<description>JMH tutorial: dead-code elimination, Blackhole, constant folding, warmup and forks, state scopes, profilers, and the mistakes that make microbenchmarks lie.</description>
<properties>
<jmh.version>1.37</jmh.version>
</properties>
<dependencies>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>benchmarks</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>25</release>
<annotationProcessorPaths>
<path>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.1</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Regenerates every file in output/. Takes roughly 6-8 minutes on a 2-vCPU host.
# Timing numbers drift from run to run; read the shape (which is bigger, by roughly how much).
set -euo pipefail
cd "$(dirname "$0")/.."
clean() { grep -vE 'JAVA_TOOL_OPTIONS|^Picked up|^WARNING|^# VM options:' || true; }
JAR="java -jar target/benchmarks.jar"
mvn -q -B package -DskipTests 2>&1 | clean
java -cp target/classes com.ankurm.jmhbasics.NaiveTimingDemo 2>&1 | clean > output/01-naive-timing.txt
$JAR DeadCodeBenchmark 2>&1 | clean > output/02-deadcode-jmh.txt
java -cp target/benchmarks.jar com.ankurm.jmhbasics.LifecycleDemo 2>&1 | clean > output/03-lifecycle.txt
$JAR ForkingBenchmark -f 0 -i 8 2>&1 | clean > output/04-forking-f0.txt
$JAR ForkingBenchmark -f 1 -i 8 2>&1 | clean > output/05-forking-f1.txt
$JAR StateScopeBenchmark 2>&1 | clean > output/06-state-scope.txt
$JAR AllocationBenchmark -prof gc 2>&1 | clean > output/07-allocation-gc.txt
$JAR SumBenchmark 2>&1 | clean > output/08-sum-param.txt
$JAR SortBenchmark -f 1 -wi 10 -w 200ms -i 5 -r 1s -bm avgt -tu us 2>&1 | clean > output/09-warmup-trace.txt
$JAR SortBenchmark -f 1 2>&1 | clean > output/10-sort-modes.txt
$JAR -lprof 2>&1 | clean > output/11-list-profilers.txt
mvn -q -B test 2>&1 | clean || true
cp target/surefire-reports/com.ankurm.jmhbasics.JmhBasicsTest.txt output/12-tests.txt
echo "Regenerated output/01-12."
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Run one demo class ad hoc, e.g.: ./scripts/run.sh NaiveTimingDemo
set -euo pipefail
cd "$(dirname "$0")/.."
mvn -q -B package -DskipTests
java -cp target/benchmarks.jar "com.ankurm.jmhbasics.$1"
@@ -0,0 +1,55 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
/**
* Every method below contains the source text {@code new Point(...)} or {@code new int[4]}.
* Whether an object is really allocated depends on whether it can escape - and only a profiler
* can tell you. Run with the built-in GC profiler:
*
* java -jar target/benchmarks.jar AllocationBenchmark -prof gc
*
* Read the {@code gc.alloc.rate.norm} column: bytes allocated per benchmark call.
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 3, time = 1)
@Fork(1)
@State(Scope.Thread)
public class AllocationBenchmark {
record Point(int x, int y) { }
int a = 3;
int b = 4;
/** The Point never leaves this method: escape analysis can replace it with two locals. */
@Benchmark
public int doesNotEscape() {
Point p = new Point(a, b);
return p.x() + p.y();
}
/** The Point is returned to JMH, so it escapes and must exist on the heap. */
@Benchmark
public Point escapes() {
return new Point(a, b);
}
/** An array that is returned: header + 4 ints, rounded up to 8-byte alignment. */
@Benchmark
public int[] arrayEscapes() {
return new int[4];
}
}
@@ -0,0 +1,82 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import java.util.concurrent.TimeUnit;
/**
* Eight benchmarks around Math.log and Math.pow. Only some of them measure what their name says.
*
* baseline empty method: the cost of JMH's own loop. Everything else is read against it.
* deadCode computes the log and throws it away -> candidate for dead-code elimination
* returned computes the log and returns it -> JMH consumes it for you
* viaBlackhole computes the log and hands it to Blackhole.consume
* logOfConstant log of a compile-time constant -> NOT folded on this JDK (see the post)
* powOfConstant pow of a compile-time constant -> folded away by the JIT
* powOfFinalField pow of a final instance field -> ALSO folded (JMHSample_10 lists this as a mistake)
* powOfField pow of a plain mutable field -> the honest, un-foldable version
*
* Run: java -jar target/benchmarks.jar DeadCodeBenchmark
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@State(Scope.Thread)
public class DeadCodeBenchmark {
static final double CONSTANT = Math.PI;
double field = Math.PI; // plain mutable field: the JIT cannot assume its value
final double finalField = Math.PI; // final: measured below to be folded exactly like a literal constant
@Benchmark
public void baseline() {
// intentionally empty
}
@Benchmark
public void deadCode() {
Math.log(field); // result discarded
}
@Benchmark
public double returned() {
return Math.log(field); // JMH feeds the return value to a Blackhole
}
@Benchmark
public void viaBlackhole(Blackhole bh) {
bh.consume(Math.log(field));
}
@Benchmark
public double logOfConstant() {
return Math.log(CONSTANT);
}
@Benchmark
public double powOfConstant() {
return Math.pow(CONSTANT, 10);
}
@Benchmark
public double powOfFinalField() {
return Math.pow(finalField, 10);
}
@Benchmark
public double powOfField() {
return Math.pow(field, 10);
}
}
@@ -0,0 +1,64 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
/**
* Why forks exist. The three benchmarks do the same work through one call site, {@code measure}.
*
* measure_1_c1 and measure_3_c1 pass an {@link AddOne}; measure_2_c2 passes an {@link AddTwo}.
*
* Fork count is deliberately NOT set here - choose it on the command line:
*
* java -jar target/benchmarks.jar ForkingBenchmark -f 0 # all three share one JVM
* java -jar target/benchmarks.jar ForkingBenchmark -f 1 # each benchmark gets a fresh JVM
*
* With -f 0 the first benchmark teaches HotSpot "this call site only ever sees AddOne". The second
* benchmark then breaks that assumption, and the third inherits the damage.
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 3, time = 1)
@State(Scope.Thread)
public class ForkingBenchmark {
public interface Step { int apply(int x); }
public static final class AddOne implements Step {
@Override public int apply(int x) { return x + 1; }
}
public static final class AddTwo implements Step {
@Override public int apply(int x) { return x + 2; }
}
final Step one = new AddOne();
final Step two = new AddTwo();
/** The single shared call site whose type profile gets polluted. */
public int measure(Step step) {
int acc = 0;
for (int i = 0; i < 10; i++) {
acc = step.apply(acc);
}
return acc;
}
@Benchmark
public int measure_1_c1() { return measure(one); }
@Benchmark
public int measure_2_c2() { return measure(two); }
@Benchmark
public int measure_3_c1() { return measure(one); }
}
@@ -0,0 +1,100 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
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 org.openjdk.jmh.runner.options.TimeValue;
import java.util.concurrent.atomic.AtomicLong;
/**
* Counts how often each {@code @Setup}/{@code @TearDown} level fires.
*
* It runs JMH with forks(0) on purpose: the benchmark then runs inside this JVM, so plain static
* counters are visible here afterwards. With a normal forked run they would live in another JVM.
*
* Configuration: 1 warmup iteration + 2 measurement iterations, 50 ms each.
*
* Run: java -cp target/benchmarks.jar com.ankurm.jmhbasics.LifecycleDemo (after mvn package)
*/
public class LifecycleDemo {
@State(Scope.Benchmark)
public static class Lifecycle {
static final AtomicLong trialSetup = new AtomicLong();
static final AtomicLong iterationSetup = new AtomicLong();
static final AtomicLong invocationSetup = new AtomicLong();
static final AtomicLong invocationTearDown = new AtomicLong();
static final AtomicLong iterationTearDown = new AtomicLong();
static final AtomicLong trialTearDown = new AtomicLong();
static final AtomicLong benchmarkCalls = new AtomicLong();
static void reset() {
trialSetup.set(0);
iterationSetup.set(0);
invocationSetup.set(0);
invocationTearDown.set(0);
iterationTearDown.set(0);
trialTearDown.set(0);
benchmarkCalls.set(0);
}
@Setup(Level.Trial)
public void setupTrial() { trialSetup.incrementAndGet(); }
@Setup(Level.Iteration)
public void setupIteration() { iterationSetup.incrementAndGet(); }
@Setup(Level.Invocation)
public void setupInvocation() { invocationSetup.incrementAndGet(); }
@TearDown(Level.Invocation)
public void tearDownInvocation() { invocationTearDown.incrementAndGet(); }
@TearDown(Level.Iteration)
public void tearDownIteration() { iterationTearDown.incrementAndGet(); }
@TearDown(Level.Trial)
public void tearDownTrial() { trialTearDown.incrementAndGet(); }
}
@Benchmark
public void work(Lifecycle state) {
Lifecycle.benchmarkCalls.incrementAndGet();
}
/** Runs the benchmark in-process and returns after JMH has finished. */
public static void run() throws RunnerException {
Lifecycle.reset();
Options opt = new OptionsBuilder()
.include(LifecycleDemo.class.getName() + ".work")
.forks(0)
.warmupIterations(1)
.warmupTime(TimeValue.milliseconds(50))
.measurementIterations(2)
.measurementTime(TimeValue.milliseconds(50))
.verbosity(org.openjdk.jmh.runner.options.VerboseMode.SILENT)
.build();
new Runner(opt).run();
}
public static void main(String[] args) throws RunnerException {
run();
System.out.println("Setup/TearDown counts for 1 warmup + 2 measurement iterations:");
System.out.println();
System.out.printf("@Setup(Level.Trial) : %d%n", Lifecycle.trialSetup.get());
System.out.printf("@Setup(Level.Iteration) : %d%n", Lifecycle.iterationSetup.get());
System.out.printf("@Setup(Level.Invocation) : %d (one per benchmark call)%n", Lifecycle.invocationSetup.get());
System.out.printf("benchmark method calls : %d%n", Lifecycle.benchmarkCalls.get());
System.out.printf("@TearDown(Level.Invocation) : %d%n", Lifecycle.invocationTearDown.get());
System.out.printf("@TearDown(Level.Iteration) : %d%n", Lifecycle.iterationTearDown.get());
System.out.printf("@TearDown(Level.Trial) : %d%n", Lifecycle.trialTearDown.get());
}
}
@@ -0,0 +1,52 @@
package com.ankurm.jmhbasics;
/**
* The benchmark most people write first: a loop around System.nanoTime().
*
* It "works" in the sense that it prints numbers. The numbers are wrong in two independent ways,
* and this program shows both on one run:
*
* 1. The loop that discards its result can be removed by the JIT, so it reports a cost close to zero.
* 2. The same code reports a different cost every round, because the JIT is still compiling it.
*
* Run: java -cp target/classes com.ankurm.jmhbasics.NaiveTimingDemo
*/
public class NaiveTimingDemo {
static final int N = 20_000_000;
/** Result thrown away - nothing observable depends on the loop. */
static void discard() {
for (int i = 1; i <= N; i++) {
Math.log(i);
}
}
/** Result kept and printed - the loop must really run. */
static double keep() {
double sum = 0;
for (int i = 1; i <= N; i++) {
sum += Math.log(i);
}
return sum;
}
public static void main(String[] args) {
System.out.println("Naive System.nanoTime() timing, " + N + " iterations per round");
System.out.println();
System.out.printf("%-7s %-22s %-22s%n", "round", "discarded (ns/op)", "kept (ns/op)");
double sink = 0;
for (int round = 1; round <= 6; round++) {
long t0 = System.nanoTime();
discard();
long t1 = System.nanoTime();
sink += keep();
long t2 = System.nanoTime();
System.out.printf("%-7d %-22.3f %-22.3f%n", round,
(t1 - t0) / (double) N, (t2 - t1) / (double) N);
}
System.out.println();
// Printing the sink is what stops the JIT removing keep() as well.
System.out.println("checksum (ignore): " + sink);
}
}
@@ -0,0 +1,50 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* One benchmark, used for two demonstrations - neither fixes the iteration counts here:
*
* 1. Warmup trace: java -jar target/benchmarks.jar SortBenchmark -f 1 -wi 10 -w 200ms -i 3 -r 1s
* JMH prints every warmup iteration, so you can watch the time per call fall as the JIT works.
*
* 2. Benchmark modes in one run: the method carries three @BenchmarkMode values, so one run reports
* throughput, average time and a sampled latency distribution with percentiles.
*/
@BenchmarkMode({Mode.Throughput, Mode.AverageTime, Mode.SampleTime})
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Thread)
public class SortBenchmark {
int[] original;
@Setup
public void fill() {
Random random = new Random(7);
original = new int[1000];
for (int i = 0; i < original.length; i++) {
original[i] = random.nextInt();
}
}
@Benchmark
public int[] sortCopy() {
int[] copy = original.clone(); // sorting in place would make every call after the first trivial
Arrays.sort(copy);
return copy;
}
}
@@ -0,0 +1,55 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Same operation, two state scopes, two benchmark threads.
*
* Scope.Thread every benchmark thread gets its own PerThread instance: no sharing, no contention
* Scope.Benchmark all benchmark threads share ONE Shared instance: they fight over one cache line
*
* The scope you pick decides whether you are measuring "increment a counter" or "increment a counter
* while another core is also incrementing it".
*
* Run: java -jar target/benchmarks.jar StateScopeBenchmark
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(2)
@Threads(2)
public class StateScopeBenchmark {
@State(Scope.Benchmark)
public static class Shared {
final AtomicLong counter = new AtomicLong();
}
@State(Scope.Thread)
public static class PerThread {
final AtomicLong counter = new AtomicLong();
}
@Benchmark
public long sharedCounter(Shared state) {
return state.counter.incrementAndGet();
}
@Benchmark
public long perThreadCounter(PerThread state) {
return state.counter.incrementAndGet();
}
}
@@ -0,0 +1,54 @@
package com.ankurm.jmhbasics;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
* {@code @Param} runs the same benchmark at several input sizes, and {@code @Setup} builds the data
* outside the measured region. Without {@code @Param} you would copy-paste three benchmark methods.
*
* Run: java -jar target/benchmarks.jar SumBenchmark
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
@State(Scope.Thread)
public class SumBenchmark {
@Param({"10", "1000", "100000"})
public int size;
int[] data;
@Setup
public void fill() {
Random random = new Random(42); // fixed seed: the same data on every run
data = new int[size];
for (int i = 0; i < size; i++) {
data[i] = random.nextInt(100);
}
}
@Benchmark
public long sum() {
long total = 0;
for (int v : data) {
total += v;
}
return total;
}
}
@@ -0,0 +1,64 @@
package com.ankurm.jmhbasics;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Pins the claims that are deterministic. Timing ratios are NOT asserted here: on a shared 2-vCPU
* host they are not stable enough to fail a build on. They live in output/ as captured runs.
*/
class JmhBasicsTest {
@Test
void annotationProcessorGeneratedBenchmarkList() throws Exception {
try (java.io.InputStream in = getClass().getResourceAsStream("/META-INF/BenchmarkList")) {
assertTrue(in != null, "annotation processor did not generate META-INF/BenchmarkList");
String list = new String(in.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8);
assertTrue(list.contains("com.ankurm.jmhbasics.jmh_generated.DeadCodeBenchmark_deadCode_jmhTest"));
assertTrue(list.contains("com.ankurm.jmhbasics.jmh_generated.SumBenchmark_sum_jmhTest"));
assertTrue(list.contains("com.ankurm.jmhbasics.jmh_generated.ForkingBenchmark_measure_2_c2_jmhTest"));
// 8 DeadCode + 1 Lifecycle + 3 Forking + 2 StateScope + 3 Allocation + 1 Sum
// + 3 Sort: one entry per @BenchmarkMode value, so SortBenchmark.sortCopy counts three times
assertEquals(21, list.lines().filter(l -> l.startsWith("JMH S")).count());
}
}
@Test
void lifecycleLevelsFireExactlyAsDocumented() throws Exception {
LifecycleDemo.run();
assertEquals(1, LifecycleDemo.Lifecycle.trialSetup.get(), "Trial setup: once per fork");
assertEquals(1, LifecycleDemo.Lifecycle.trialTearDown.get());
assertEquals(3, LifecycleDemo.Lifecycle.iterationSetup.get(), "Iteration setup: 1 warmup + 2 measurement");
assertEquals(3, LifecycleDemo.Lifecycle.iterationTearDown.get());
long calls = LifecycleDemo.Lifecycle.benchmarkCalls.get();
assertTrue(calls > 0);
assertEquals(calls, LifecycleDemo.Lifecycle.invocationSetup.get(), "Invocation setup: once per call");
assertEquals(calls, LifecycleDemo.Lifecycle.invocationTearDown.get());
}
@Test
void benchmarkBodiesComputeTheRightAnswers() {
SumBenchmark sum = new SumBenchmark();
sum.size = 1000;
sum.fill();
long expected = 0;
for (int v : sum.data) expected += v;
assertEquals(expected, sum.sum());
SortBenchmark sort = new SortBenchmark();
sort.fill();
int[] sorted = sort.sortCopy();
for (int i = 1; i < sorted.length; i++) assertTrue(sorted[i - 1] <= sorted[i]);
// The original must be untouched, otherwise later iterations would sort an already-sorted array.
boolean originalUnsorted = false;
for (int i = 1; i < sort.original.length; i++) if (sort.original[i - 1] > sort.original[i]) originalUnsorted = true;
assertTrue(originalUnsorted);
ForkingBenchmark f = new ForkingBenchmark();
assertEquals(10, f.measure_1_c1());
assertEquals(20, f.measure_2_c2());
}
}