"Every rev (factor) of the dragon is a warning. Every gearshift bears the load (memory). The dragon unleashes its might at 1.0f, packing your data into the map like it never even felt it."
The Dragon Feed is the internal bulk-loading engine of ChaosTree's B-Tree and B+Tree architectures. Instead of utilizing standard iterative insertions—which suffer from pointer chasing, tree rebalancing, and heavy GC allocation rates—the Dragon Feed directly ingests raw sorted matrices and compiles them into contiguous memory pages.
The Dragon Feed exposes a factor parameter, acting as the transmission for the engine.
It dictates exactly how tightly the internal arrays should be packed during bulk ingestion.
- 0.5f (First Gear): 50% capacity. Leaves room for future random insertions without triggering node splits.
Highest initial memory footprint.
- 1.0f (Top Gear): 100% capacity. Zero wasted bytes. Maximum CPU cache line saturation. Pure throughput.
Benchmark performed on: Intel Core i5-13450HX (16 cores), G1GC, default heap (5803 MB).
Two ingestion paths were measured. importFlatMatrix bulk-copies a pre-built sorted matrix directly
into node arrays via System.arraycopy; buildFromSorted walks a sorted iterator
(the same style of construction TreeMap uses) and builds nodes incrementally. All 30-fork averages,
times in microseconds per operation.
Reproduce with:
java -jar ct-benchmark/target/benchmarks.jar WriteHeavyMap.bulkLoadBPlusTree \
-p size=10000,100000,1000000,10000000 \
-p factor=0.5f,0.75f,1.0f \
-f 3 -wi 10 -w 1 -r 1 -i 10 -tu us -rf text -rff bulkLoad.txt
java -jar ct-benchmark/target/benchmarks.jar WriteHeavyMap.importFlatMatrixBPlusTree \
-p size=10000,100000,1000000,10000000 \
-p factor=0.5f,0.75f,1.0f \
-f 3 -wi 10 -w 1 -r 1 -i 10 -tu us -rf text -rff dragonFeed.txt
| Factor | 10,000 | 100,000 | 1,000,000 | 10,000,000 |
|---|---|---|---|---|
| 0.5f | 11.545 ± 0.166 | 145.733 ± 0.895 | 1466.230 ± 7.196 | 16729.324 ± 511.749 |
| 0.75f | 9.700 ± 0.121 | 98.792 ± 0.499 | 1015.293 ± 4.962 | 13006.075 ± 629.287 |
| 1.0f | 7.319 ± 0.032 | 75.207 ± 0.288 | 791.220 ± 16.122 | 9726.478 ± 109.668 |
All values in microseconds per operation, rows are factor, columns are size — read down a column to compare factors at fixed size, or across a row to see how a single factor scales with size.
Packing factor directly sizes the underlying flat array and resize threshold here, so it's a first-order
effect: 1.0f beats 0.5f by roughly 35–42% across every size.
Same path, finer-grained factor sweep, with JMH's gc.alloc.rate.norm profiler enabled to see
bytes allocated per op directly. Allocation drops almost linearly as factor rises — less slack per node means
fewer nodes, which means less array overhead per element — and time tracks it closely.
java -jar ct-benchmark/target/benchmarks.jar WriteHeavyMap.importFlatMatrixBPlusTree \
-p size=1000000 \
-p factor=0.5f,0.6f,0.7f,0.8f,0.9f,1.0f \
-f 3 -wi 10 -w 1 -r 1 -i 10 -tu ms -prof gc -rf text -rff dragonFeedMemory.txt
| Factor | Time (ms/op) | Alloc Rate (MB/sec) | Alloc/op (B/op) | Tree Size (MB) | GC Count | GC Time (ms) |
|---|---|---|---|---|---|---|
| 0.5f | 1.483 ± 0.009 | 11273.263 ± 69.080 | 17538210.268 | 16.73 | 248 | 384 |
| 0.6f | 1.241 ± 0.005 | 11213.114 ± 46.660 | 14592160.594 | 13.91 | 258 | 384 |
| 0.7f | 1.097 ± 0.007 | 10976.941 ± 70.795 | 12628127.598 | 12.04 | 259 | 329 |
| 0.8f | 0.954 ± 0.006 | 10950.163 ± 69.089 | 10952094.603 | 10.44 | 258 | 354 |
| 0.9f | 0.845 ± 0.004 | 10901.292 ± 46.037 | 9664125.850 | 9.22 | 282 | 337 |
| 1.0f | 0.777 ± 0.008 | 10682.879 ± 114.768 | 8701725.380 | 8.30 | 279 | 343 |
Tree Size (MB) is Alloc/op converted to MB — for a bulk import benchmark this is
effectively the resident size of the built tree, since each op constructs one fresh tree from scratch.
0.5f → 1.0f shrinks it from 16.73 MB to 8.30 MB, roughly halving memory footprint for
1,000,000 entries, while time drops ~48% in step.
| Factor | 10,000 | 100,000 | 1,000,000 | 10,000,000 |
|---|---|---|---|---|
| 0.5f | 74.251 ± 0.847 | 700.342 ± 15.937 | 7640.535 ± 68.402 | 82775.216 ± 4449.711 |
| 0.75f | 72.601 ± 0.235 | 705.388 ± 14.247 | 7597.012 ± 35.819 | 83992.645 ± 4933.130 |
| 1.0f | 73.762 ± 1.303 | 689.406 ± 6.192 | 7619.628 ± 46.503 | 84430.291 ± 5037.158 |
All values in microseconds per operation, same layout as above — down a column compares factors at fixed size, across a row shows one factor's scaling.
Here the three factors sit within each other's error bars at every size — packing density only controls node occupancy, and it's a second-order effect compared to the per-entry comparator/iterator/split cost that dominates this path.
// Use the Dragon at top gear (1.0f)
// It is advised to leave a space for future operation.
// 1. Prepare your sorted data matrix
Integer[] sortedKeys = {1, 2, 3, 4, 5};
String[] values = {"A", "B", "C", "D", "E"};
Object[][] matrix = new Object[][]{sortedKeys, values};
// 2. Feed the Dragon
BPlusTreeMap fastMap = new BPlusTreeMap<>(
BPlusTreeMap.Builder.degree(64)
.factor(0.8f) // 80% packing density, leaves room for runtime inserts
.importFlatMatrix(matrix)
.build()
);
blast[0] contains the keys, and
blast[1] contains the values.
null in the value array at that index.
0.5f and 1.0f.
A factor of 0.75f is highly recommended for bulk loading.
This packs the nodes densely while leaving exactly enough buffer room
to prevent future insertions from triggering massive, cascading
split operations.
Hold the Chaos!
blast[0] is the sorted keys and
blast[1] is the mapped values.
factor
The node fill factor, restricted to the range [0.5, 1.0].
// Awaken the Dragon at top gear (1.0f)
Object[][] blast = new Object[][]{sortedKeys, values};
float factor = 1.0f;
BPlusTreeMap<Integer, String> map = new BPlusTreeMap<>();
//this creates a default degree of 64 and comparator null!
map.importFlatMatrix(blast, factor);
WARNING: The provided iterator MUST yield elements in strict ascending order according to this tree's comparator. If the data is unsorted, the tree structure will be corrupted.
FILL FACTOR:
0.5 and 1.0 representing
how full to pack each node.
1.0f for read-only data.
0.9f is used for bulk loading in my tree.
1.0f, but after that,
any insert or remove operation will trigger massive split, merge,
borrow, and array-shifting operations.
Parameters:
0.5 and 1.0 representing
how full to pack each node.
// Use the Dragon at top gear (1.0f)
BPlusTreeMap<Integer, String> map = new BPlusTreeMap<>();
//this creates a default degree of 64 and comparator null!
map.buildFromSorted(iterator, factor);