diff --git a/README.md b/README.md index 75d82ba..98eb66e 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ article; each module's own README has that article's version table, quickstart, | [`strings`](strings/) | Java Strings Deep Dive: Interning, StringBuilder, Text Blocks, and the Concatenation Benchmark | | [`collections`](collections/) | How to Sort a HashMap by Value (and Key) in Java | | [`nio2`](nio2/) | Java NIO.2 File API: Files, Path, WatchService, and Streaming Large Files Without OOM | +| [`list-benchmarks`](list-benchmarks/) | ArrayList vs LinkedList in 2026: JMH Benchmarks and Why LinkedList Rarely Wins | ## License diff --git a/list-benchmarks/README.md b/list-benchmarks/README.md new file mode 100644 index 0000000..aee574f --- /dev/null +++ b/list-benchmarks/README.md @@ -0,0 +1,81 @@ +# list-benchmarks + +Companion code for the ankurm.com post *"ArrayList vs LinkedList in 2026: JMH Benchmarks and Why +LinkedList Rarely Wins."* Module in `java-core-examples`, the Java-core series. + +## Versions this was built and tested against + +| Component | Version | Notes | +|---|---|---| +| JDK | 25.0.4.1+1 (Temurin, LTS) | | +| JMH | 1.37 | | +| JUnit Jupiter | 5.11.0 | Correctness tests only - do not read these as throughput proof. | +| Maven | 3.9.11 | | +| Hardware | shared multi-tenant container | See the honesty note below - this is not an isolated benchmarking box. | + +## Quickstart + +```bash +export JAVA_HOME=/path/to/jdk-25 +mvn package +java -jar target/benchmarks.jar ListOpsBenchmark +java -jar target/benchmarks.jar DequeBenchmark +java -cp target/classes com.ankurm.listbenchmarks.IteratorRemovalDemo +``` + +`scripts/run-all.sh` regenerates every file in `output/`. `scripts/run.sh [jmh-args]` runs +one benchmark ad hoc. + +## What's in here + +| File | What it shows | +|---|---| +| `src/main/java/.../ListOpsBenchmark.java` | `ArrayList` vs `LinkedList`: append, front-insert, middle-insert, random `get`, full iteration. Each mutating benchmark inserts then immediately removes, so list size stays constant across the whole run. | +| `src/main/java/.../DequeBenchmark.java` | `ArrayDeque` vs `LinkedList` vs `ArrayList` used as a stack/queue: push/pop at the head, offer/poll at the tail. | +| `src/main/java/.../IteratorRemovalDemo.java` | The one case `LinkedList` can win: removing through an `Iterator` cursor instead of by index. Plain `nanoTime()`, not JMH - read as illustrative. | +| `src/test/java/.../CorrectnessTest.java` | Sanity checks: both lists agree on contents and ordering; `ArrayDeque` genuinely does not implement `List`. | +| `output/01-list-ops-sweep.txt` | Full JMH report for `ListOpsBenchmark`, sizes 1,000 and 100,000. | +| `output/02-deque-ops-sweep.txt` | Full JMH report for `DequeBenchmark`, sizes 1,000 and 100,000. | +| `output/03-iterator-removal-demo.txt` | Captured run of the iterator-removal illustration. | +| `output/04-correctness-test.txt` | JUnit run (4/4 passing). | + +## Reading the numbers honestly (shared container, not an isolated box) + +This run shares CPU with five other concurrent build/benchmark workloads on the same host - JMH +was still run one class at a time, back to back, never overlapping with itself, but it cannot see +or control what else is scheduled on the machine. Several rows carry an error bar wider than the +mean (`DequeBenchmark.offerPollTail ArrayDeque @1000`: 203,926 ± 985,600 ops/ms), which is +the signature of scheduler noise, not of the JVM doing something strange. Treat every number here +as **directional**, and prefer the **ratios** between rows measured in the same run over the +absolute ops/ms. + +With that caveat: + +- **`addFront` is the one operation where `LinkedList` clearly and consistently wins** - flat + ~43,000 ops/ms at both 1,000 and 100,000 elements, because it is an O(1) pointer write + regardless of size. `ArrayList.addFront` falls from ~6,600 ops/ms at 1,000 elements to ~53 + ops/ms at 100,000 - two orders of magnitude - because every call shifts the whole array by one. +- **`addMiddle` favors `ArrayList` by roughly 20-25x at both sizes**, even though both + implementations are doing O(n) work to get there. `ArrayList`'s half is a single + `System.arraycopy`; `LinkedList`'s is a pointer-chasing walk node by node to find the midpoint, + and that walk is the slower half of "O(n) to reach the middle, O(1) to unlink." +- **`getRandom` and `iterateSum` both favor `ArrayList`**, the first by ~2,000x at 100,000 + elements (random access on `LinkedList` is a full traversal per call), the second by ~4-5x even + though iteration is O(n) either way - this is the cache-locality gap the post discusses: one + contiguous `int[]`-backed array versus following a chain of individually-allocated `Node` + objects scattered across the heap. +- **For stack/queue-shaped work, `ArrayDeque` is the one to reach for, not `LinkedList`** - + `pushPopHead` favors `ArrayDeque` over `LinkedList` by ~3.6x at 1,000 elements, and `ArrayDeque` + never degrades to `ArrayList`'s O(n) head-shift problem (`ArrayList.pushPopHead` collapses to 56 + ops/ms at 100,000 elements, a 1,800x gap from `ArrayDeque`'s 103,406). +- **The iterator-removal illustration (`output/03`) is the sharpest number in this repository**: + removing every third element of a 200,000-element list through an `Iterator` cursor takes + **1,314 ms with `ArrayList` and 5.0 ms with `LinkedList` - a 262x gap** - because + `ArrayList.remove(index)` still shifts the tail of the array on every call even when the index + came from an iterator already sitting on it, while `LinkedList.Iterator.remove()` unlinks the + node the cursor already holds. This is also, honestly, close to the *only* realistic case where + reaching for `LinkedList` over `ArrayList` is the right call in 2026 code. + +## License + +MIT - see the [repo-wide LICENSE](../LICENSE). diff --git a/list-benchmarks/output/01-list-ops-sweep.txt b/list-benchmarks/output/01-list-ops-sweep.txt new file mode 100644 index 0000000..b3a0fce --- /dev/null +++ b/list-benchmarks/output/01-list-ops-sweep.txt @@ -0,0 +1,576 @@ +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 0.00% complete, ETA 00:01:40 +# Fork: 1 of 1 +# Warmup Iteration 1: 162114.256 ops/ms +# Warmup Iteration 2: 163227.532 ops/ms +Iteration 1: 174081.389 ops/ms +Iteration 2: 181016.166 ops/ms +Iteration 3: 178653.294 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd": + 177916.950 ±(99.9%) 64318.989 ops/ms [Average] + (min, avg, max) = (174081.389, 177916.950, 181016.166), stdev = 3525.541 + CI (99.9%): [113597.961, 242235.939] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 5.00% complete, ETA 00:01:43 +# Fork: 1 of 1 +# Warmup Iteration 1: 85814.984 ops/ms +# Warmup Iteration 2: 77594.269 ops/ms +Iteration 1: 84356.568 ops/ms +Iteration 2: 83619.315 ops/ms +Iteration 3: 72410.302 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd": + 80128.728 ±(99.9%) 122132.789 ops/ms [Average] + (min, avg, max) = (72410.302, 80128.728, 84356.568), stdev = 6694.510 + CI (99.9%): [≈ 0, 202261.517] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 10.00% complete, ETA 00:01:37 +# Fork: 1 of 1 +# Warmup Iteration 1: 74571.629 ops/ms +# Warmup Iteration 2: 42845.531 ops/ms +Iteration 1: 44822.469 ops/ms +Iteration 2: 46759.673 ops/ms +Iteration 3: 47030.988 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd": + 46204.377 ±(99.9%) 21973.304 ops/ms [Average] + (min, avg, max) = (44822.469, 46204.377, 47030.988), stdev = 1204.431 + CI (99.9%): [24231.073, 68177.680] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 15.00% complete, ETA 00:01:32 +# Fork: 1 of 1 +# Warmup Iteration 1: 82182.548 ops/ms +# Warmup Iteration 2: 48790.149 ops/ms +Iteration 1: 41766.671 ops/ms +Iteration 2: 42867.682 ops/ms +Iteration 3: 38072.881 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addEnd": + 40902.411 ±(99.9%) 45819.499 ops/ms [Average] + (min, avg, max) = (38072.881, 40902.411, 42867.682), stdev = 2511.521 + CI (99.9%): [≈ 0, 86721.910] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addFront +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 20.00% complete, ETA 00:01:26 +# Fork: 1 of 1 +# Warmup Iteration 1: 5358.049 ops/ms +# Warmup Iteration 2: 5705.109 ops/ms +Iteration 1: 6860.775 ops/ms +Iteration 2: 7014.548 ops/ms +Iteration 3: 5879.974 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addFront": + 6585.099 ±(99.9%) 11228.597 ops/ms [Average] + (min, avg, max) = (5879.974, 6585.099, 7014.548), stdev = 615.477 + CI (99.9%): [≈ 0, 17813.697] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addFront +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 25.00% complete, ETA 00:01:21 +# Fork: 1 of 1 +# Warmup Iteration 1: 35.381 ops/ms +# Warmup Iteration 2: 45.342 ops/ms +Iteration 1: 53.127 ops/ms +Iteration 2: 53.514 ops/ms +Iteration 3: 53.857 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addFront": + 53.499 ±(99.9%) 6.659 ops/ms [Average] + (min, avg, max) = (53.127, 53.499, 53.857), stdev = 0.365 + CI (99.9%): [46.840, 60.158] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addFront +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 30.00% complete, ETA 00:01:16 +# Fork: 1 of 1 +# Warmup Iteration 1: 79349.690 ops/ms +# Warmup Iteration 2: 46393.515 ops/ms +Iteration 1: 42718.334 ops/ms +Iteration 2: 41957.209 ops/ms +Iteration 3: 44017.076 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addFront": + 42897.540 ±(99.9%) 19001.947 ops/ms [Average] + (min, avg, max) = (41957.209, 42897.540, 44017.076), stdev = 1041.561 + CI (99.9%): [23895.592, 61899.487] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addFront +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 35.00% complete, ETA 00:01:11 +# Fork: 1 of 1 +# Warmup Iteration 1: 23976.443 ops/ms +# Warmup Iteration 2: 30056.587 ops/ms +Iteration 1: 44990.603 ops/ms +Iteration 2: 44365.967 ops/ms +Iteration 3: 40586.360 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addFront": + 43314.310 ±(99.9%) 43475.313 ops/ms [Average] + (min, avg, max) = (40586.360, 43314.310, 44990.603), stdev = 2383.029 + CI (99.9%): [≈ 0, 86789.623] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 40.00% complete, ETA 00:01:05 +# Fork: 1 of 1 +# Warmup Iteration 1: 16685.105 ops/ms +# Warmup Iteration 2: 15174.527 ops/ms +Iteration 1: 15288.973 ops/ms +Iteration 2: 13588.771 ops/ms +Iteration 3: 14183.387 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle": + 14353.710 ±(99.9%) 15740.742 ops/ms [Average] + (min, avg, max) = (13588.771, 14353.710, 15288.973), stdev = 862.803 + CI (99.9%): [≈ 0, 30094.453] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 45.00% complete, ETA 00:00:59 +# Fork: 1 of 1 +# Warmup Iteration 1: 66.502 ops/ms +# Warmup Iteration 2: 108.785 ops/ms +Iteration 1: 127.539 ops/ms +Iteration 2: 124.636 ops/ms +Iteration 3: 124.909 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle": + 125.695 ±(99.9%) 29.247 ops/ms [Average] + (min, avg, max) = (124.636, 125.695, 127.539), stdev = 1.603 + CI (99.9%): [96.447, 154.942] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 50.00% complete, ETA 00:00:54 +# Fork: 1 of 1 +# Warmup Iteration 1: 536.060 ops/ms +# Warmup Iteration 2: 606.294 ops/ms +Iteration 1: 605.980 ops/ms +Iteration 2: 605.421 ops/ms +Iteration 3: 614.996 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle": + 608.799 ±(99.9%) 98.048 ops/ms [Average] + (min, avg, max) = (605.421, 608.799, 614.996), stdev = 5.374 + CI (99.9%): [510.751, 706.847] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 55.00% complete, ETA 00:00:49 +# Fork: 1 of 1 +# Warmup Iteration 1: 4.930 ops/ms +# Warmup Iteration 2: 4.993 ops/ms +Iteration 1: 5.047 ops/ms +Iteration 2: 5.149 ops/ms +Iteration 3: 5.061 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.addMiddle": + 5.086 ±(99.9%) 1.014 ops/ms [Average] + (min, avg, max) = (5.047, 5.086, 5.149), stdev = 0.056 + CI (99.9%): [4.072, 6.100] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 60.00% complete, ETA 00:00:43 +# Fork: 1 of 1 +# Warmup Iteration 1: 72989.593 ops/ms +# Warmup Iteration 2: 73568.287 ops/ms +Iteration 1: 74557.252 ops/ms +Iteration 2: 76233.965 ops/ms +Iteration 3: 75917.857 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom": + 75569.691 ±(99.9%) 16253.874 ops/ms [Average] + (min, avg, max) = (74557.252, 75569.691, 76233.965), stdev = 890.930 + CI (99.9%): [59315.818, 91823.565] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 65.00% complete, ETA 00:00:37 +# Fork: 1 of 1 +# Warmup Iteration 1: 39087.453 ops/ms +# Warmup Iteration 2: 42258.929 ops/ms +Iteration 1: 41705.407 ops/ms +Iteration 2: 37824.238 ops/ms +Iteration 3: 41843.150 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom": + 40457.598 ±(99.9%) 41624.817 ops/ms [Average] + (min, avg, max) = (37824.238, 40457.598, 41843.150), stdev = 2281.597 + CI (99.9%): [≈ 0, 82082.415] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 70.00% complete, ETA 00:00:32 +# Fork: 1 of 1 +# Warmup Iteration 1: 2573.620 ops/ms +# Warmup Iteration 2: 2593.939 ops/ms +Iteration 1: 2578.054 ops/ms +Iteration 2: 1819.430 ops/ms +Iteration 3: 2350.190 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom": + 2249.225 ±(99.9%) 7101.544 ops/ms [Average] + (min, avg, max) = (1819.430, 2249.225, 2578.054), stdev = 389.260 + CI (99.9%): [≈ 0, 9350.769] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 75.00% complete, ETA 00:00:26 +# Fork: 1 of 1 +# Warmup Iteration 1: 19.457 ops/ms +# Warmup Iteration 2: 19.681 ops/ms +Iteration 1: 19.396 ops/ms +Iteration 2: 19.827 ops/ms +Iteration 3: 18.270 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.getRandom": + 19.165 ±(99.9%) 14.662 ops/ms [Average] + (min, avg, max) = (18.270, 19.165, 19.827), stdev = 0.804 + CI (99.9%): [4.503, 33.826] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 80.00% complete, ETA 00:00:21 +# Fork: 1 of 1 +# Warmup Iteration 1: 2001.245 ops/ms +# Warmup Iteration 2: 1969.018 ops/ms +Iteration 1: 1869.923 ops/ms +Iteration 2: 1798.606 ops/ms +Iteration 3: 1915.867 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum": + 1861.466 ±(99.9%) 1077.953 ops/ms [Average] + (min, avg, max) = (1798.606, 1861.466, 1915.867), stdev = 59.086 + CI (99.9%): [783.513, 2939.418] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 85.00% complete, ETA 00:00:16 +# Fork: 1 of 1 +# Warmup Iteration 1: 13.767 ops/ms +# Warmup Iteration 2: 14.572 ops/ms +Iteration 1: 14.687 ops/ms +Iteration 2: 13.892 ops/ms +Iteration 3: 12.981 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum": + 13.853 ±(99.9%) 15.571 ops/ms [Average] + (min, avg, max) = (12.981, 13.853, 14.687), stdev = 0.854 + CI (99.9%): [≈ 0, 29.425] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 90.00% complete, ETA 00:00:10 +# Fork: 1 of 1 +# Warmup Iteration 1: 515.059 ops/ms +# Warmup Iteration 2: 563.552 ops/ms +Iteration 1: 535.605 ops/ms +Iteration 2: 561.710 ops/ms +Iteration 3: 561.907 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum": + 553.074 ±(99.9%) 276.013 ops/ms [Average] + (min, avg, max) = (535.605, 553.074, 561.907), stdev = 15.129 + CI (99.9%): [277.061, 829.088] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 95.00% complete, ETA 00:00:05 +# Fork: 1 of 1 +# Warmup Iteration 1: 2.898 ops/ms +# Warmup Iteration 2: 2.928 ops/ms +Iteration 1: 2.959 ops/ms +Iteration 2: 2.940 ops/ms +Iteration 3: 2.979 ops/ms + + +Result "com.ankurm.listbenchmarks.ListOpsBenchmark.iterateSum": + 2.959 ±(99.9%) 0.351 ops/ms [Average] + (min, avg, max) = (2.940, 2.959, 2.979), stdev = 0.019 + CI (99.9%): [2.608, 3.310] (assumes normal distribution) + + +# Run complete. Total time: 00:01:47 + +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 (impl) (size) Mode Cnt Score Error Units +ListOpsBenchmark.addEnd ArrayList 1000 thrpt 3 177916.950 ± 64318.989 ops/ms +ListOpsBenchmark.addEnd ArrayList 100000 thrpt 3 80128.728 ± 122132.789 ops/ms +ListOpsBenchmark.addEnd LinkedList 1000 thrpt 3 46204.377 ± 21973.304 ops/ms +ListOpsBenchmark.addEnd LinkedList 100000 thrpt 3 40902.411 ± 45819.499 ops/ms +ListOpsBenchmark.addFront ArrayList 1000 thrpt 3 6585.099 ± 11228.597 ops/ms +ListOpsBenchmark.addFront ArrayList 100000 thrpt 3 53.499 ± 6.659 ops/ms +ListOpsBenchmark.addFront LinkedList 1000 thrpt 3 42897.540 ± 19001.947 ops/ms +ListOpsBenchmark.addFront LinkedList 100000 thrpt 3 43314.310 ± 43475.313 ops/ms +ListOpsBenchmark.addMiddle ArrayList 1000 thrpt 3 14353.710 ± 15740.742 ops/ms +ListOpsBenchmark.addMiddle ArrayList 100000 thrpt 3 125.695 ± 29.247 ops/ms +ListOpsBenchmark.addMiddle LinkedList 1000 thrpt 3 608.799 ± 98.048 ops/ms +ListOpsBenchmark.addMiddle LinkedList 100000 thrpt 3 5.086 ± 1.014 ops/ms +ListOpsBenchmark.getRandom ArrayList 1000 thrpt 3 75569.691 ± 16253.874 ops/ms +ListOpsBenchmark.getRandom ArrayList 100000 thrpt 3 40457.598 ± 41624.817 ops/ms +ListOpsBenchmark.getRandom LinkedList 1000 thrpt 3 2249.225 ± 7101.544 ops/ms +ListOpsBenchmark.getRandom LinkedList 100000 thrpt 3 19.165 ± 14.662 ops/ms +ListOpsBenchmark.iterateSum ArrayList 1000 thrpt 3 1861.466 ± 1077.953 ops/ms +ListOpsBenchmark.iterateSum ArrayList 100000 thrpt 3 13.853 ± 15.571 ops/ms +ListOpsBenchmark.iterateSum LinkedList 1000 thrpt 3 553.074 ± 276.013 ops/ms +ListOpsBenchmark.iterateSum LinkedList 100000 thrpt 3 2.959 ± 0.351 ops/ms + diff --git a/list-benchmarks/output/02-deque-ops-sweep.txt b/list-benchmarks/output/02-deque-ops-sweep.txt new file mode 100644 index 0000000..e5ddcb8 --- /dev/null +++ b/list-benchmarks/output/02-deque-ops-sweep.txt @@ -0,0 +1,352 @@ +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail +# Parameters: (impl = ArrayDeque, size = 1000) + +# Run progress: 0.00% complete, ETA 00:01:00 +# Fork: 1 of 1 +# Warmup Iteration 1: 225049.942 ops/ms +# Warmup Iteration 2: 216387.082 ops/ms +Iteration 1: 260204.948 ops/ms +Iteration 2: 152481.625 ops/ms +Iteration 3: 199092.571 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail": + 203926.381 ±(99.9%) 985600.610 ops/ms [Average] + (min, avg, max) = (152481.625, 203926.381, 260204.948), stdev = 54024.095 + CI (99.9%): [≈ 0, 1189526.991] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail +# Parameters: (impl = ArrayDeque, size = 100000) + +# Run progress: 8.33% complete, ETA 00:00:58 +# Fork: 1 of 1 +# Warmup Iteration 1: 73559.495 ops/ms +# Warmup Iteration 2: 88126.623 ops/ms +Iteration 1: 82011.079 ops/ms +Iteration 2: 84538.326 ops/ms +Iteration 3: 60837.052 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail": + 75795.485 ±(99.9%) 237457.872 ops/ms [Average] + (min, avg, max) = (60837.052, 75795.485, 84538.326), stdev = 13015.867 + CI (99.9%): [≈ 0, 313253.357] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 16.67% complete, ETA 00:00:54 +# Fork: 1 of 1 +# Warmup Iteration 1: 82032.730 ops/ms +# Warmup Iteration 2: 80584.591 ops/ms +Iteration 1: 79301.950 ops/ms +Iteration 2: 88128.376 ops/ms +Iteration 3: 87233.185 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail": + 84887.837 ±(99.9%) 88631.345 ops/ms [Average] + (min, avg, max) = (79301.950, 84887.837, 88128.376), stdev = 4858.183 + CI (99.9%): [≈ 0, 173519.182] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 25.00% complete, ETA 00:00:49 +# Fork: 1 of 1 +# Warmup Iteration 1: 102529.616 ops/ms +# Warmup Iteration 2: 75609.806 ops/ms +Iteration 1: 86058.866 ops/ms +Iteration 2: 85350.743 ops/ms +Iteration 3: 85011.734 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail": + 85473.781 ±(99.9%) 9747.596 ops/ms [Average] + (min, avg, max) = (85011.734, 85473.781, 86058.866), stdev = 534.299 + CI (99.9%): [75726.185, 95221.376] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 33.33% complete, ETA 00:00:43 +# Fork: 1 of 1 +# Warmup Iteration 1: 212378.579 ops/ms +# Warmup Iteration 2: 218438.486 ops/ms +Iteration 1: 245793.910 ops/ms +Iteration 2: 245870.397 ops/ms +Iteration 3: 242681.893 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail": + 244782.067 ±(99.9%) 33189.080 ops/ms [Average] + (min, avg, max) = (242681.893, 244782.067, 245870.397), stdev = 1819.205 + CI (99.9%): [211592.987, 277971.147] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 41.67% complete, ETA 00:00:37 +# Fork: 1 of 1 +# Warmup Iteration 1: 94135.159 ops/ms +# Warmup Iteration 2: 95652.428 ops/ms +Iteration 1: 98077.379 ops/ms +Iteration 2: 100976.549 ops/ms +Iteration 3: 98379.804 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.offerPollTail": + 99144.577 ±(99.9%) 29075.456 ops/ms [Average] + (min, avg, max) = (98077.379, 99144.577, 100976.549), stdev = 1593.724 + CI (99.9%): [70069.122, 128220.033] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead +# Parameters: (impl = ArrayDeque, size = 1000) + +# Run progress: 50.00% complete, ETA 00:00:32 +# Fork: 1 of 1 +# Warmup Iteration 1: 298481.803 ops/ms +# Warmup Iteration 2: 312401.613 ops/ms +Iteration 1: 299087.963 ops/ms +Iteration 2: 298466.157 ops/ms +Iteration 3: 299175.622 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead": + 298909.914 ±(99.9%) 7056.600 ops/ms [Average] + (min, avg, max) = (298466.157, 298909.914, 299175.622), stdev = 386.796 + CI (99.9%): [291853.313, 305966.514] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead +# Parameters: (impl = ArrayDeque, size = 100000) + +# Run progress: 58.33% complete, ETA 00:00:26 +# Fork: 1 of 1 +# Warmup Iteration 1: 106969.537 ops/ms +# Warmup Iteration 2: 90907.103 ops/ms +Iteration 1: 104740.942 ops/ms +Iteration 2: 105037.254 ops/ms +Iteration 3: 100438.523 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead": + 103405.573 ±(99.9%) 46955.844 ops/ms [Average] + (min, avg, max) = (100438.523, 103405.573, 105037.254), stdev = 2573.808 + CI (99.9%): [56449.730, 150361.417] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead +# Parameters: (impl = LinkedList, size = 1000) + +# Run progress: 66.67% complete, ETA 00:00:21 +# Fork: 1 of 1 +# Warmup Iteration 1: 108757.170 ops/ms +# Warmup Iteration 2: 76136.263 ops/ms +Iteration 1: 79754.897 ops/ms +Iteration 2: 87449.217 ops/ms +Iteration 3: 83960.921 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead": + 83721.678 ±(99.9%) 70288.226 ops/ms [Average] + (min, avg, max) = (79754.897, 83721.678, 87449.217), stdev = 3852.735 + CI (99.9%): [13433.452, 154009.905] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead +# Parameters: (impl = LinkedList, size = 100000) + +# Run progress: 75.00% complete, ETA 00:00:16 +# Fork: 1 of 1 +# Warmup Iteration 1: 110204.142 ops/ms +# Warmup Iteration 2: 74155.001 ops/ms +Iteration 1: 86320.175 ops/ms +Iteration 2: 83052.660 ops/ms +Iteration 3: 84954.624 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead": + 84775.820 ±(99.9%) 29939.400 ops/ms [Average] + (min, avg, max) = (83052.660, 84775.820, 86320.175), stdev = 1641.080 + CI (99.9%): [54836.420, 114715.220] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead +# Parameters: (impl = ArrayList, size = 1000) + +# Run progress: 83.33% complete, ETA 00:00:10 +# Fork: 1 of 1 +# Warmup Iteration 1: 11212.051 ops/ms +# Warmup Iteration 2: 11024.015 ops/ms +Iteration 1: 11274.928 ops/ms +Iteration 2: 11150.226 ops/ms +Iteration 3: 10960.108 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead": + 11128.421 ±(99.9%) 2892.341 ops/ms [Average] + (min, avg, max) = (10960.108, 11128.421, 11274.928), stdev = 158.539 + CI (99.9%): [8236.080, 14020.762] (assumes normal distribution) + + +# JMH version: 1.37 +# VM version: JDK 25.0.4.1, OpenJDK 64-Bit Server VM, 25.0.4.1+1-LTS +# VM invoker: /tmp/tools/jdk-25.0.4.1+1/bin/java +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: 2 iterations, 1 s each +# Measurement: 3 iterations, 1 s each +# Timeout: 10 min per iteration +# Threads: 1 thread, will synchronize iterations +# Benchmark mode: Throughput, ops/time +# Benchmark: com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead +# Parameters: (impl = ArrayList, size = 100000) + +# Run progress: 91.67% complete, ETA 00:00:05 +# Fork: 1 of 1 +# Warmup Iteration 1: 42.851 ops/ms +# Warmup Iteration 2: 57.144 ops/ms +Iteration 1: 54.588 ops/ms +Iteration 2: 57.561 ops/ms +Iteration 3: 55.350 ops/ms + + +Result "com.ankurm.listbenchmarks.DequeBenchmark.pushPopHead": + 55.833 ±(99.9%) 28.174 ops/ms [Average] + (min, avg, max) = (54.588, 55.833, 57.561), stdev = 1.544 + CI (99.9%): [27.658, 84.007] (assumes normal distribution) + + +# Run complete. Total time: 00:01:04 + +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 (impl) (size) Mode Cnt Score Error Units +DequeBenchmark.offerPollTail ArrayDeque 1000 thrpt 3 203926.381 ± 985600.610 ops/ms +DequeBenchmark.offerPollTail ArrayDeque 100000 thrpt 3 75795.485 ± 237457.872 ops/ms +DequeBenchmark.offerPollTail LinkedList 1000 thrpt 3 84887.837 ± 88631.345 ops/ms +DequeBenchmark.offerPollTail LinkedList 100000 thrpt 3 85473.781 ± 9747.596 ops/ms +DequeBenchmark.offerPollTail ArrayList 1000 thrpt 3 244782.067 ± 33189.080 ops/ms +DequeBenchmark.offerPollTail ArrayList 100000 thrpt 3 99144.577 ± 29075.456 ops/ms +DequeBenchmark.pushPopHead ArrayDeque 1000 thrpt 3 298909.914 ± 7056.600 ops/ms +DequeBenchmark.pushPopHead ArrayDeque 100000 thrpt 3 103405.573 ± 46955.844 ops/ms +DequeBenchmark.pushPopHead LinkedList 1000 thrpt 3 83721.678 ± 70288.226 ops/ms +DequeBenchmark.pushPopHead LinkedList 100000 thrpt 3 84775.820 ± 29939.400 ops/ms +DequeBenchmark.pushPopHead ArrayList 1000 thrpt 3 11128.421 ± 2892.341 ops/ms +DequeBenchmark.pushPopHead ArrayList 100000 thrpt 3 55.833 ± 28.174 ops/ms + diff --git a/list-benchmarks/output/03-iterator-removal-demo.txt b/list-benchmarks/output/03-iterator-removal-demo.txt new file mode 100644 index 0000000..817d00e --- /dev/null +++ b/list-benchmarks/output/03-iterator-removal-demo.txt @@ -0,0 +1,10 @@ +Removing every 3rd element of a 200000-element list via Iterator.remove() + +ArrayList (iterator.remove, every 3rd): 1,314,187,777 ns (1314.19 ms) +LinkedList (iterator.remove, every 3rd): 5,019,906 ns (5.02 ms) +Ratio (ArrayList time / LinkedList time): 261.80x + +ArrayList.remove(index) still shifts every element after the removed one, +even though the iterator was already positioned on it. LinkedList's +iterator.remove() unlinks the node the cursor holds - no shift, no search. +This is the one removal pattern where LinkedList is the right call. diff --git a/list-benchmarks/output/04-correctness-test.txt b/list-benchmarks/output/04-correctness-test.txt new file mode 100644 index 0000000..b5ccd18 --- /dev/null +++ b/list-benchmarks/output/04-correctness-test.txt @@ -0,0 +1,4 @@ +------------------------------------------------------------------------------- +Test set: com.ankurm.listbenchmarks.CorrectnessTest +------------------------------------------------------------------------------- +Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.259 s -- in com.ankurm.listbenchmarks.CorrectnessTest diff --git a/list-benchmarks/pom.xml b/list-benchmarks/pom.xml new file mode 100644 index 0000000..3f88449 --- /dev/null +++ b/list-benchmarks/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + com.ankurm + java-core-examples + 1.0 + + + list-benchmarks + list-benchmarks + ArrayList vs LinkedList vs ArrayDeque: JMH throughput for add/get/iterate/remove, and why LinkedList rarely wins. + + + 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/list-benchmarks/scripts/run-all.sh b/list-benchmarks/scripts/run-all.sh new file mode 100755 index 0000000..24a14c6 --- /dev/null +++ b/list-benchmarks/scripts/run-all.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Regenerates every file in output/. Run from the list-benchmarks module directory. +set -euo pipefail +cd "$(dirname "$0")/.." + +mvn -q -B package + +java -jar target/benchmarks.jar ListOpsBenchmark 2>&1 \ + | grep -vE 'JAVA_TOOL_OPTIONS|Djavax\.net\.ssl|^WARNING|^Benchmark result is saved to' \ + > output/01-list-ops-sweep.txt + +java -jar target/benchmarks.jar DequeBenchmark 2>&1 \ + | grep -vE 'JAVA_TOOL_OPTIONS|Djavax\.net\.ssl|^WARNING|^Benchmark result is saved to' \ + > output/02-deque-ops-sweep.txt + +java -cp target/classes com.ankurm.listbenchmarks.IteratorRemovalDemo 2>&1 \ + | grep -vE 'JAVA_TOOL_OPTIONS|Djavax\.net\.ssl|^WARNING' \ + > output/03-iterator-removal-demo.txt + +mvn -q -B test +cp target/surefire-reports/com.ankurm.listbenchmarks.CorrectnessTest.txt output/04-correctness-test.txt + +echo "Regenerated output/01-04." diff --git a/list-benchmarks/scripts/run.sh b/list-benchmarks/scripts/run.sh new file mode 100755 index 0000000..6911ea7 --- /dev/null +++ b/list-benchmarks/scripts/run.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Run one JMH benchmark class ad hoc, e.g.: ./scripts/run.sh ListOpsBenchmark -t 1 +set -euo pipefail +cd "$(dirname "$0")/.." +mvn -q -B package -DskipTests +java -jar target/benchmarks.jar "$@" diff --git a/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/DequeBenchmark.java b/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/DequeBenchmark.java new file mode 100644 index 0000000..2171368 --- /dev/null +++ b/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/DequeBenchmark.java @@ -0,0 +1,84 @@ +package com.ankurm.listbenchmarks; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Stack/queue-shaped workload: push a value in, pop it back out, repeatedly, on a deque that + * already holds {@code size} elements. This is the workload the article's "when ArrayDeque fits + * instead of either" section is about - it is neither "random access" (ArrayList's strength) nor + * "insert given a list reference" (the only thing LinkedList is actually good at), it is the far + * more common "push/pop/offer/poll from one or both ends" shape. + * + *

{@code ArrayDeque} is not a {@link List} - it deliberately does not implement + * {@code get(index)} - which is itself part of the article's point: reach for it when you need a + * stack or a queue, not a list, and the API stops you from doing index math you did not mean to + * do. + * + *

See {@code output/02-deque-ops-sweep.txt} for the captured run. + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1) +@Measurement(iterations = 3, time = 1) +@Fork(1) +public class DequeBenchmark { + + @Param({"ArrayDeque", "LinkedList", "ArrayList"}) + public String impl; + + @Param({"1000", "100000"}) + public int size; + + private Deque deque; // used for ArrayDeque and LinkedList + private List arrayListAsStack; // used only for the ArrayList "anti-pattern" arm + + @Setup(Level.Trial) + public void setup() { + switch (impl) { + case "ArrayDeque" -> deque = new ArrayDeque<>(size); + case "LinkedList" -> deque = new LinkedList<>(); + case "ArrayList" -> arrayListAsStack = new ArrayList<>(size); + default -> throw new IllegalStateException(impl); + } + if ("ArrayList".equals(impl)) { + for (int i = 0; i < size; i++) arrayListAsStack.add(i); + } else { + for (int i = 0; i < size; i++) deque.addLast(i); + } + } + + /** push then pop at the head - the natural operation for a real Deque. */ + @Benchmark + public void pushPopHead(Blackhole bh) { + if (arrayListAsStack != null) { + // The only way to get "push/pop at the front" semantics out of a List is + // add(0, x) / remove(0) - an O(n) shift on every single call. That cost IS the point. + arrayListAsStack.add(0, -1); + bh.consume(arrayListAsStack.remove(0)); + } else { + deque.addFirst(-1); + bh.consume(deque.pollFirst()); + } + } + + /** offer then poll at the tail - the natural operation for a real Queue. */ + @Benchmark + public void offerPollTail(Blackhole bh) { + if (arrayListAsStack != null) { + arrayListAsStack.add(-1); + bh.consume(arrayListAsStack.remove(arrayListAsStack.size() - 1)); + } else { + deque.addLast(-1); + bh.consume(deque.pollLast()); + } + } +} diff --git a/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/IteratorRemovalDemo.java b/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/IteratorRemovalDemo.java new file mode 100644 index 0000000..7748535 --- /dev/null +++ b/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/IteratorRemovalDemo.java @@ -0,0 +1,73 @@ +package com.ankurm.listbenchmarks; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * The one shape of removal where {@code LinkedList} can actually win: removing through an + * {@link Iterator} cursor you are already standing on, instead of by index. + * + *

{@code ArrayList.remove(index)} is always an array shift, even when the index came from an + * iterator that was already sitting on the element - the iterator does not save it from the + * shift. {@code LinkedList}'s {@code Iterator.remove()} unlinks the current node directly, no + * traversal needed, because the cursor already holds the node reference. + * + *

This is a plain wall-clock illustration with {@code System.nanoTime()}, not a JMH + * benchmark - JIT warmup and GC are not controlled for the way {@link ListOpsBenchmark} controls + * for them. Read it as "yes, this case is real", not as a throughput number to quote. The JMH + * numbers for the general case are in {@code output/01-list-ops-sweep.txt} and + * {@code output/02-deque-ops-sweep.txt}. + */ +public final class IteratorRemovalDemo { + + private IteratorRemovalDemo() {} + + public static void main(String[] args) { + int size = 200_000; + System.out.println("Removing every 3rd element of a " + size + "-element list via Iterator.remove()"); + System.out.println(); + + long arrayListNanos = removeEveryThird(buildArrayList(size)); + long linkedListNanos = removeEveryThird(buildLinkedList(size)); + + System.out.printf("ArrayList (iterator.remove, every 3rd): %,d ns (%.2f ms)%n", + arrayListNanos, arrayListNanos / 1_000_000.0); + System.out.printf("LinkedList (iterator.remove, every 3rd): %,d ns (%.2f ms)%n", + linkedListNanos, linkedListNanos / 1_000_000.0); + System.out.printf("Ratio (ArrayList time / LinkedList time): %.2fx%n", + (double) arrayListNanos / linkedListNanos); + System.out.println(); + System.out.println("ArrayList.remove(index) still shifts every element after the removed one,"); + System.out.println("even though the iterator was already positioned on it. LinkedList's"); + System.out.println("iterator.remove() unlinks the node the cursor holds - no shift, no search."); + System.out.println("This is the one removal pattern where LinkedList is the right call."); + } + + private static long removeEveryThird(List list) { + long start = System.nanoTime(); + int i = 0; + Iterator it = list.iterator(); + while (it.hasNext()) { + it.next(); + if (i % 3 == 0) { + it.remove(); + } + i++; + } + return System.nanoTime() - start; + } + + private static List buildArrayList(int size) { + List l = new ArrayList<>(size); + for (int i = 0; i < size; i++) l.add(i); + return l; + } + + private static List buildLinkedList(int size) { + List l = new LinkedList<>(); + for (int i = 0; i < size; i++) l.add(i); + return l; + } +} diff --git a/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/ListOpsBenchmark.java b/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/ListOpsBenchmark.java new file mode 100644 index 0000000..db10e9d --- /dev/null +++ b/list-benchmarks/src/main/java/com/ankurm/listbenchmarks/ListOpsBenchmark.java @@ -0,0 +1,87 @@ +package com.ankurm.listbenchmarks; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Throughput comparison of {@link ArrayList} and {@link LinkedList} on the five operations + * that actually show up in real code: append, random-index read, full iteration, and + * insert/remove at the front and in the middle. + * + *

Every mutating benchmark does an insert immediately followed by the matching removal so + * the list's size (and therefore the cost profile) stays constant across the whole measurement + * window instead of drifting as the iteration runs. That is what keeps {@code addFront} and + * {@code addFront} comparable at iteration 1 and iteration 1,000,000 of the same run. + * + *

Referenced from the "ArrayList vs LinkedList in 2026" post - see + * {@code output/01-list-ops-sweep.txt} for the captured run this repository ships. + */ +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Thread) +@Warmup(iterations = 2, time = 1) +@Measurement(iterations = 3, time = 1) +@Fork(1) +public class ListOpsBenchmark { + + @Param({"ArrayList", "LinkedList"}) + public String impl; + + @Param({"1000", "100000"}) + public int size; + + private List list; + private final Random rnd = new Random(42); + + @Setup(Level.Trial) + public void setup() { + list = "ArrayList".equals(impl) ? new ArrayList<>(size) : new LinkedList<>(); + for (int i = 0; i < size; i++) { + list.add(i); + } + } + + /** Append one element at the end, then remove it. Net size unchanged. */ + @Benchmark + public void addEnd(Blackhole bh) { + list.add(size, -1); + bh.consume(list.remove(size)); + } + + /** Insert at index 0, then remove index 0. The operation ArrayList is worst at. */ + @Benchmark + public void addFront(Blackhole bh) { + list.add(0, -1); + bh.consume(list.remove(0)); + } + + /** Insert at the midpoint, then remove it again. */ + @Benchmark + public void addMiddle(Blackhole bh) { + int mid = size / 2; + list.add(mid, -1); + bh.consume(list.remove(mid)); + } + + /** Random-access read by index - no mutation, size never changes. */ + @Benchmark + public int getRandom() { + return list.get(rnd.nextInt(size)); + } + + /** Full forward iteration summing every element, via the iterator (the idiomatic way). */ + @Benchmark + public long iterateSum() { + long sum = 0; + for (int v : list) { + sum += v; + } + return sum; + } +} diff --git a/list-benchmarks/src/test/java/com/ankurm/listbenchmarks/CorrectnessTest.java b/list-benchmarks/src/test/java/com/ankurm/listbenchmarks/CorrectnessTest.java new file mode 100644 index 0000000..f6b1d2c --- /dev/null +++ b/list-benchmarks/src/test/java/com/ankurm/listbenchmarks/CorrectnessTest.java @@ -0,0 +1,69 @@ +package com.ankurm.listbenchmarks; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Sanity checks only - these do not prove throughput, they prove the two data structures stay + * behaviourally interchangeable through {@link List} so the benchmark's "same workload, two + * implementations" framing is actually true, and pin the one behavioural difference the article + * leans on: {@code ArrayDeque} is not a {@link List}. + */ +class CorrectnessTest { + + @Test + void arrayListAndLinkedListAgreeOnContents() { + List a = new ArrayList<>(); + List l = new LinkedList<>(); + for (int i = 0; i < 1000; i++) { + a.add(i); + l.add(i); + } + assertEquals(a, l, "List.equals is content-based, not implementation-based"); + assertEquals(a.get(500), l.get(500)); + } + + @Test + void addFrontShiftsArrayListButNotLinkedList() { + List a = new ArrayList<>(List.of(1, 2, 3)); + List l = new LinkedList<>(List.of(1, 2, 3)); + a.add(0, 0); + l.add(0, 0); + assertEquals(List.of(0, 1, 2, 3), a); + assertEquals(List.of(0, 1, 2, 3), l); + // Both produce the same *result* - the benchmark exists because the *cost* differs. + } + + @Test + void arrayDequeIsNotAList() { + Deque d = new ArrayDeque<>(); + assertThrows(ClassCastException.class, () -> { + @SuppressWarnings({"unchecked", "rawtypes"}) + List asList = (List) d; // compiles only via raw type + unchecked cast + asList.get(0); + }, "ArrayDeque deliberately does not implement List - there is no get(index)"); + } + + @Test + void arrayDequeAndLinkedListAgreeOnStackOrder() { + Deque ad = new ArrayDeque<>(); + Deque ll = new LinkedList<>(); + for (int v : new int[] {1, 2, 3}) { + ad.push(v); + ll.push(v); + } + // Both are LIFO via push/pop - same observable order, different internal layout. + assertEquals(3, ad.pop()); + assertEquals(3, ll.pop()); + assertEquals(2, ad.pop()); + assertEquals(2, ll.pop()); + } +} diff --git a/pom.xml b/pom.xml index ec85326..03a8228 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ strings collections nio2 + list-benchmarks