From db129fb1ce41029d2b87959661b8aa02131daea5 Mon Sep 17 00:00:00 2001 From: Ankur Mhatre Date: Sun, 8 Nov 2026 18:30:47 +0000 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_017ic4b6Fj2JNt7dRev4pZPe --- README.md | 1 + jmh-basics/README.md | 53 +++ jmh-basics/output/01-naive-timing.txt | 11 + jmh-basics/output/02-deadcode-jmh.txt | 343 ++++++++++++++++++ jmh-basics/output/03-lifecycle.txt | 9 + jmh-basics/output/04-forking-f0.txt | 117 ++++++ jmh-basics/output/05-forking-f1.txt | 114 ++++++ jmh-basics/output/06-state-scope.txt | 97 +++++ jmh-basics/output/07-allocation-gc.txt | 203 +++++++++++ jmh-basics/output/08-sum-param.txt | 108 ++++++ jmh-basics/output/09-warmup-trace.txt | 52 +++ jmh-basics/output/10-sort-modes.txt | 186 ++++++++++ jmh-basics/output/11-list-profilers.txt | 28 ++ jmh-basics/output/12-tests.txt | 4 + jmh-basics/pom.xml | 83 +++++ jmh-basics/scripts/run-all.sh | 26 ++ jmh-basics/scripts/run.sh | 6 + .../ankurm/jmhbasics/AllocationBenchmark.java | 55 +++ .../ankurm/jmhbasics/DeadCodeBenchmark.java | 82 +++++ .../ankurm/jmhbasics/ForkingBenchmark.java | 64 ++++ .../com/ankurm/jmhbasics/LifecycleDemo.java | 100 +++++ .../com/ankurm/jmhbasics/NaiveTimingDemo.java | 52 +++ .../com/ankurm/jmhbasics/SortBenchmark.java | 50 +++ .../ankurm/jmhbasics/StateScopeBenchmark.java | 55 +++ .../com/ankurm/jmhbasics/SumBenchmark.java | 54 +++ .../com/ankurm/jmhbasics/JmhBasicsTest.java | 64 ++++ pom.xml | 1 + 27 files changed, 2018 insertions(+) create mode 100644 jmh-basics/README.md create mode 100644 jmh-basics/output/01-naive-timing.txt create mode 100644 jmh-basics/output/02-deadcode-jmh.txt create mode 100644 jmh-basics/output/03-lifecycle.txt create mode 100644 jmh-basics/output/04-forking-f0.txt create mode 100644 jmh-basics/output/05-forking-f1.txt create mode 100644 jmh-basics/output/06-state-scope.txt create mode 100644 jmh-basics/output/07-allocation-gc.txt create mode 100644 jmh-basics/output/08-sum-param.txt create mode 100644 jmh-basics/output/09-warmup-trace.txt create mode 100644 jmh-basics/output/10-sort-modes.txt create mode 100644 jmh-basics/output/11-list-profilers.txt create mode 100644 jmh-basics/output/12-tests.txt create mode 100644 jmh-basics/pom.xml create mode 100755 jmh-basics/scripts/run-all.sh create mode 100755 jmh-basics/scripts/run.sh create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/AllocationBenchmark.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/DeadCodeBenchmark.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/ForkingBenchmark.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/LifecycleDemo.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/NaiveTimingDemo.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/SortBenchmark.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/StateScopeBenchmark.java create mode 100644 jmh-basics/src/main/java/com/ankurm/jmhbasics/SumBenchmark.java create mode 100644 jmh-basics/src/test/java/com/ankurm/jmhbasics/JmhBasicsTest.java diff --git a/README.md b/README.md index da2a30a..0678028 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ article; each module's own README has that article's version table, quickstart, | [`sorting`](sorting/) | Java Comparator & Comparable: The Complete Guide | | [`annotation-processor`](annotation-processor/) | Custom Annotations and Annotation Processors in Java: Generate Code at Compile Time | | [`reflection`](reflection/) | Java Reflection and MethodHandles in 2026: How They Work and What They Cost | +| [`jmh-basics`](jmh-basics/) | JMH Tutorial: Writing Java Microbenchmarks That Don't Lie | ## License diff --git a/jmh-basics/README.md b/jmh-basics/README.md new file mode 100644 index 0000000..f94e8fa --- /dev/null +++ b/jmh-basics/README.md @@ -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 ` 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). diff --git a/jmh-basics/output/01-naive-timing.txt b/jmh-basics/output/01-naive-timing.txt new file mode 100644 index 0000000..9fab3a4 --- /dev/null +++ b/jmh-basics/output/01-naive-timing.txt @@ -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 diff --git a/jmh-basics/output/02-deadcode-jmh.txt b/jmh-basics/output/02-deadcode-jmh.txt new file mode 100644 index 0000000..b52e533 --- /dev/null +++ b/jmh-basics/output/02-deadcode-jmh.txt @@ -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 diff --git a/jmh-basics/output/03-lifecycle.txt b/jmh-basics/output/03-lifecycle.txt new file mode 100644 index 0000000..3fe0185 --- /dev/null +++ b/jmh-basics/output/03-lifecycle.txt @@ -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 diff --git a/jmh-basics/output/04-forking-f0.txt b/jmh-basics/output/04-forking-f0.txt new file mode 100644 index 0000000..8469d08 --- /dev/null +++ b/jmh-basics/output/04-forking-f0.txt @@ -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 diff --git a/jmh-basics/output/05-forking-f1.txt b/jmh-basics/output/05-forking-f1.txt new file mode 100644 index 0000000..2bfbc64 --- /dev/null +++ b/jmh-basics/output/05-forking-f1.txt @@ -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 diff --git a/jmh-basics/output/06-state-scope.txt b/jmh-basics/output/06-state-scope.txt new file mode 100644 index 0000000..3606fca --- /dev/null +++ b/jmh-basics/output/06-state-scope.txt @@ -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 diff --git a/jmh-basics/output/07-allocation-gc.txt b/jmh-basics/output/07-allocation-gc.txt new file mode 100644 index 0000000..286ef98 --- /dev/null +++ b/jmh-basics/output/07-allocation-gc.txt @@ -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 diff --git a/jmh-basics/output/08-sum-param.txt b/jmh-basics/output/08-sum-param.txt new file mode 100644 index 0000000..335ab17 --- /dev/null +++ b/jmh-basics/output/08-sum-param.txt @@ -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 diff --git a/jmh-basics/output/09-warmup-trace.txt b/jmh-basics/output/09-warmup-trace.txt new file mode 100644 index 0000000..19a78d4 --- /dev/null +++ b/jmh-basics/output/09-warmup-trace.txt @@ -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 diff --git a/jmh-basics/output/10-sort-modes.txt b/jmh-basics/output/10-sort-modes.txt new file mode 100644 index 0000000..a3be88f --- /dev/null +++ b/jmh-basics/output/10-sort-modes.txt @@ -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 diff --git a/jmh-basics/output/11-list-profilers.txt b/jmh-basics/output/11-list-profilers.txt new file mode 100644 index 0000000..35c9cfb --- /dev/null +++ b/jmh-basics/output/11-list-profilers.txt @@ -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: +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=. + dtraceasm: +[sudo: dtrace: command not found +] + perf: +[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ] + perfasm: +[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ] + perfc2c: +[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ] + perfnorm: +[Cannot run program "perf": Exec failed, error: 2 (No such file or directory) ] + xperfasm: +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) ] + diff --git a/jmh-basics/output/12-tests.txt b/jmh-basics/output/12-tests.txt new file mode 100644 index 0000000..e9f2f8f --- /dev/null +++ b/jmh-basics/output/12-tests.txt @@ -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 diff --git a/jmh-basics/pom.xml b/jmh-basics/pom.xml new file mode 100644 index 0000000..00e58dc --- /dev/null +++ b/jmh-basics/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + com.ankurm + java-core-examples + 1.0 + + + jmh-basics + jmh-basics + JMH tutorial: dead-code elimination, Blackhole, constant folding, warmup and forks, state scopes, profilers, and the mistakes that make microbenchmarks lie. + + + 1.37 + + + + + org.openjdk.jmh + jmh-core + ${jmh.version} + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + org.junit.jupiter + junit-jupiter + 5.11.0 + test + + + + + benchmarks + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + 25 + + + org.openjdk.jmh + jmh-generator-annprocess + ${jmh.version} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.1 + + + package + shade + + + + org.openjdk.jmh.Main + + + + + + + + + diff --git a/jmh-basics/scripts/run-all.sh b/jmh-basics/scripts/run-all.sh new file mode 100755 index 0000000..d687704 --- /dev/null +++ b/jmh-basics/scripts/run-all.sh @@ -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." diff --git a/jmh-basics/scripts/run.sh b/jmh-basics/scripts/run.sh new file mode 100755 index 0000000..ad616a6 --- /dev/null +++ b/jmh-basics/scripts/run.sh @@ -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" diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/AllocationBenchmark.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/AllocationBenchmark.java new file mode 100644 index 0000000..9ab56f3 --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/AllocationBenchmark.java @@ -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]; + } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/DeadCodeBenchmark.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/DeadCodeBenchmark.java new file mode 100644 index 0000000..ec77232 --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/DeadCodeBenchmark.java @@ -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); + } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/ForkingBenchmark.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/ForkingBenchmark.java new file mode 100644 index 0000000..0012c3b --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/ForkingBenchmark.java @@ -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); } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/LifecycleDemo.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/LifecycleDemo.java new file mode 100644 index 0000000..733e9b2 --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/LifecycleDemo.java @@ -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()); + } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/NaiveTimingDemo.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/NaiveTimingDemo.java new file mode 100644 index 0000000..93d8da8 --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/NaiveTimingDemo.java @@ -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); + } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/SortBenchmark.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/SortBenchmark.java new file mode 100644 index 0000000..dab7c67 --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/SortBenchmark.java @@ -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; + } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/StateScopeBenchmark.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/StateScopeBenchmark.java new file mode 100644 index 0000000..3132605 --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/StateScopeBenchmark.java @@ -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(); + } +} diff --git a/jmh-basics/src/main/java/com/ankurm/jmhbasics/SumBenchmark.java b/jmh-basics/src/main/java/com/ankurm/jmhbasics/SumBenchmark.java new file mode 100644 index 0000000..5004a6c --- /dev/null +++ b/jmh-basics/src/main/java/com/ankurm/jmhbasics/SumBenchmark.java @@ -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; + } +} diff --git a/jmh-basics/src/test/java/com/ankurm/jmhbasics/JmhBasicsTest.java b/jmh-basics/src/test/java/com/ankurm/jmhbasics/JmhBasicsTest.java new file mode 100644 index 0000000..d790c13 --- /dev/null +++ b/jmh-basics/src/test/java/com/ankurm/jmhbasics/JmhBasicsTest.java @@ -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()); + } +} diff --git a/pom.xml b/pom.xml index 4c506d7..c2422a2 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ sorting annotation-processor reflection + jmh-basics