Arena Map Benchmark: ChaosTree vs JDK, Fastutil, Eclipse
One repeatable mixed workload of get, put and remove on sorted maps,
at 10K, 100K and 1M entries. Score is time per operation (ns/op), single-threaded. Lower is better.
Violet marks the winner. Error (JMH 99.9% confidence interval) is drawn at lower
opacity than the data.
1. What this benchmark measures
The map is filled once with size keys, inserted by plain put in shuffled order (seed 42).
Then every invocation runs 100,000 operations read from a pre-generated pool of 2M packed (op, key) entries. Keys
are uniform over 0 … 2×size-1. The cursor advances between invocations, so the map evolves and no
rebuild happens between them. Keys and values are pre-boxed Integers, so the timed loop does no RNG
work and allocates nothing. Results go to a Blackhole. The op stream is identical for every map at a
given seed and mix.
A mix such as 50,25,25 means 50% get, 25% put, 25% remove. Mixes with equal put and remove (50,25,25,
20,40,40, 80,10,10) hold the tree near half full. Mixes with 0% remove saturate the tree,
so they measure lookup and overwrite on a full tree.
2. System and reproduction
CPU
Intel Core i5-13450HX
Heap
Pinned: -Xms4g -Xmx4g -XX:+AlwaysPreTouch
GC
-XX:+UseParallelGC, the same for every map
JMH settings
3 forks, 10 warmup x 1s, 10 measurement x 1s (30 samples per row).
@OperationsPerInvocation(100000), so scores are ns/op.
Score ± JMH error. Rows sorted by mean over the six mixes.
Time per operation by mix, 100K entries (ns/op)
Time table, 100K (ns/op)
Map
50,25,25
20,40,40
80,10,10
70,30,0
50,50,0
30,70,0
Mean
ChaosBPlusTree
211.3 ±0.6
217.1 ±0.9
202.9 ±2.5
221.9 ±5.6
210.8 ±3.1
215.0 ±4.2
213.2 ±1.4
ChaosBTree
207.7 ±1.6
217.9 ±1.6
200.8 ±2.2
215.2 ±3.8
216.6 ±3.1
225.0 ±10.2
213.8 ±2.0
FastutilAVLIntNative
284.1 ±2.7
299.2 ±4.4
238.0 ±1.9
212.3 ±5.0
220.5 ±1.9
226.2 ±1.8
246.7 ±1.3
FastutilRBTIntNative
292.8 ±3.4
305.3 ±2.6
236.4 ±1.5
208.2 ±2.1
215.6 ±3.6
223.9 ±4.0
247.0 ±1.2
JDKTreeMap
344.8 ±2.3
401.3 ±26.9
298.4 ±3.0
314.2 ±2.5
320.9 ±2.3
322.2 ±3.2
333.6 ±4.6
FastutilRBTObj
383.3 ±3.6
402.7 ±3.9
346.0 ±13.9
301.5 ±1.4
310.7 ±1.6
319.3 ±2.6
343.9 ±2.5
FastutilAVLObj
388.0 ±14.5
409.9 ±14.4
334.0 ±1.4
308.3 ±3.0
322.2 ±4.7
329.4 ±2.4
348.6 ±3.6
ChaosRBT
382.7 ±26.0
412.5 ±21.1
324.0 ±3.9
329.7 ±3.9
334.6 ±13.4
326.9 ±2.6
351.7 ±6.1
ChaosAVL
397.9 ±5.4
434.3 ±9.1
337.5 ±2.3
316.9 ±3.0
311.5 ±5.6
315.0 ±18.9
352.2 ±3.8
EclipseRBT
355.6 ±17.0
400.4 ±23.2
336.3 ±41.2
357.1 ±24.7
403.8 ±40.2
337.6 ±10.2
365.1 ±11.6
JDKConcurrentSkipListMap
625.6 ±17.9
651.8 ±7.7
563.4 ±7.0
543.0 ±2.6
551.0 ±4.6
565.5 ±9.4
583.4 ±3.9
Score ± JMH error. Rows sorted by mean over the six mixes.
Time per operation by mix, 1M entries (ns/op)
Time table, 1M (ns/op)
Map
50,25,25
20,40,40
80,10,10
70,30,0
50,50,0
30,70,0
Mean
ChaosBPlusTree
509.9 ±4.2
537.0 ±1.5
467.7 ±8.9
534.3 ±6.5
554.6 ±5.1
584.8 ±3.1
531.4 ±2.2
ChaosBTree
508.7 ±1.8
542.5 ±2.4
466.5 ±1.7
526.7 ±3.4
569.1 ±6.9
595.7 ±5.3
534.9 ±1.7
FastutilRBTIntNative
580.5 ±5.7
666.6 ±5.8
451.3 ±11.8
519.4 ±5.3
555.4 ±3.0
580.0 ±20.4
558.9 ±4.3
FastutilAVLIntNative
596.2 ±18.6
693.2 ±6.8
460.8 ±4.9
532.9 ±9.3
582.9 ±9.2
606.0 ±6.2
578.7 ±4.2
ChaosAVL
1045.6 ±6.3
1103.7 ±7.7
919.4 ±4.9
1010.9 ±19.8
971.4 ±9.5
946.9 ±12.6
999.6 ±4.6
EclipseRBT
1084.1 ±5.7
1174.5 ±35.0
934.2 ±9.2
1085.0 ±11.6
1242.4 ±103.0
1110.2 ±14.6
1105.1 ±18.5
FastutilRBTObj
1109.5 ±7.1
1271.3 ±63.7
999.8 ±49.6
1059.0 ±23.9
1094.0 ±14.9
1109.7 ±17.4
1107.2 ±14.6
ChaosRBT
1058.5 ±5.9
1137.3 ±33.8
938.6 ±6.2
1175.3 ±155.8
1124.1 ±36.7
1232.1 ±59.0
1111.0 ±29.0
FastutilAVLObj
1081.7 ±12.6
1190.4 ±19.8
934.6 ±9.8
1137.4 ±45.7
1180.1 ±13.0
1144.7 ±32.2
1111.5 ±10.5
JDKTreeMap
1086.8 ±40.1
1149.8 ±57.5
926.9 ±46.2
1329.2 ±118.3
1159.6 ±43.1
1093.9 ±22.4
1124.4 ±25.5
JDKConcurrentSkipListMap
1909.8 ±54.3
1999.9 ±38.3
1703.3 ±127.6
1881.5 ±62.5
1922.4 ±6.9
2048.6 ±37.1
1910.9 ±26.9
Score ± JMH error. Rows sorted by mean over the six mixes.
Scaling with size (ns/op)
Mean over the six mixes. Black lines are Chaos maps, grey lines are the others. The violet dot is the fastest map at that size.
Mean time by size (ns/op)
Map
10K
100K
100K / 10K
1M
1M / 100K
ChaosBPlusTree
127.3 ±0.2
213.2 ±1.4
1.67x
531.4 ±2.2
2.49x
ChaosBTree
127.7 ±0.3
213.8 ±2.0
1.67x
534.9 ±1.7
2.50x
FastutilRBTIntNative
126.0 ±0.3
247.0 ±1.2
1.96x
558.9 ±4.3
2.26x
FastutilAVLIntNative
123.0 ±0.2
246.7 ±1.3
2.01x
578.7 ±4.2
2.35x
ChaosAVL
154.6 ±0.5
352.2 ±3.8
2.28x
999.6 ±4.6
2.84x
EclipseRBT
146.0 ±0.5
365.1 ±11.6
2.50x
1105.1 ±18.5
3.03x
FastutilRBTObj
158.1 ±0.5
343.9 ±2.5
2.18x
1107.2 ±14.6
3.22x
ChaosRBT
155.4 ±0.7
351.7 ±6.1
2.26x
1111.0 ±29.0
3.16x
FastutilAVLObj
157.2 ±0.4
348.6 ±3.6
2.22x
1111.5 ±10.5
3.19x
JDKTreeMap
145.5 ±0.5
333.6 ±4.6
2.29x
1124.4 ±25.5
3.37x
JDKConcurrentSkipListMap
244.7 ±1.2
583.4 ±3.9
2.38x
1910.9 ±26.9
3.28x
Mean ns/op over the six mixes. Violet = fastest, tie = error overlaps the fastest. Sorted by the
largest size.
4. How to read this page
Violet bar / violet bold cell
Lowest mean in that column. The winner.
Outlined bar / violet cell
Its error interval overlaps the winner's. Treat as a statistical tie.
Black bar
Slower than the winner beyond the error.
Pale whisker / pale ±value
JMH error, at lower opacity than the data.
5. Caveats
ChaosBPlusTree and ChaosAVL at 100K are from a rerun of the identical command, because the first pass had
wide errors (±32 and ±70). Other rows with wide errors (EclipseRBT, ChaosRBT) may be inflated the same way. Same
way there were three combination which had error varying around might be 10~12% which did go through reran.
No rebuild between invocations. The map evolves across the run, so results reflect steady-state churn,
not a fresh build.
0%-remove mixes run on a saturated tree and are not comparable to the put=remove mixes.
Boxing. Every map except the two IntNative ones uses boxed Integer keys. The
IntNative maps are Fastutil at its best (no boxing, no Comparable calls), so they are
a ceiling reference, not a like-for-like competitor. They come from a separate class and run with the same
workload code, seed and mixes.
JDKConcurrentSkipListMap pays for concurrency this single-threaded benchmark does not use.
6. Findings:
FastUtil lacks an O(N) bulk-load: FastUtil does not have an O(N) bulk-load capability (like buildFromSorted()).
When initialized with sorted data, it falls back to sequential O(log N) put() calls.
To maintain a level playing field, bulk-loading benchmarks were excluded from this arena.
Where is MapDB? MapDB was initially included, but even its in-memory modes rely heavily on serialization
and byte-array copying. This generated a massive GC footprint that severely degraded benchmark stability and
polluted the heap, even at the lowest datasets. It was ultimately excluded for fairness.
N-ary Tree Iterator Escape Analysis (EA): B+Tree iterators are streamlined enough that they consistently benefit
from JVM Escape Analysis and are completely scalar-replaced (zero heap allocation). In contrast, B-Tree iterators require
complex traversal state, which defeats EA and forces a 40B heap allocation per iterator. To strictly adhere to JDK interface
contracts, aggressive iterator reuse/flyweight patterns were avoided, leaving the allocation intact.