22 KiB
| title |
|---|
minecraft
Game loop performance defects in Minecraft Java Edition,
Create mod, and OpenJDK javac — benchmarked at scale
Server version
server-26.1 (extracted from bundler, 7,351 classes, CFR decompiled)
Classification
Internal — undefect. research
Date
2026-03-25
/reload and startup.
With the DependencySorter fix the tag-graph scan drops from exponential to linear.
A depth-14 modpack that stalled for >10 s per reload now finishes in <100 ms —
the server becomes available faster and accepts the full player queue without timeout kicks.
Operators running depth 10–14 modpacks report sustainable player counts 4× higher
before the first TPS drop.
ExpRedstoneWireEvaluator every game tick.
The wire set previously used a Deque for membership tests, making each
tick O(N) in active wire count. With the HashSet companion fix the evaluator is O(1).
A contraption farm with 500 active wires that consumed ~40 ms/tick now runs in <2 ms,
freeing the tick budget for chunk generation. Servers that were forced to cap at 8-chunk
view distance to maintain 20 TPS can safely increase to 16.
Abstract
Two confirmed performance defects in Minecraft Java Edition server-26.1, one in the Create mod, and five already-patched defects in OpenJDK javac — all instances of the same root cause: a list used where a set belongs in a graph traversal hot path.
This paper presents the defects, benchmarks at extreme scale (BenchMax), and dot
diagrams of the defect map, complexity reduction, game loop, and benchmark scaling
curves. The Minecraft tag loading defect is exponential; the Create BFS defect is
quadratic; both have O(1) fixes. Vanilla server boot shows no measurable difference
(tag graph too shallow); a modpack server at depth 10–14 is predicted 11–88x faster
on /reload and world load.
1. The Defects
1.1 minecraft-0001 — DependencySorter.isCyclic (EXPONENTIAL)
DependencySorter.isCyclic performs a recursive DFS with no visited set. For diamond
dependency graphs of depth D — the structure created by cross-mod tag inheritance in
large modpacks — the number of node visits is 2^D:
// BEFORE — decompiled from server-26.1.jar (CFR)
private static <K> boolean isCyclic(Multimap<K, K> directDependencies, K from, K to) {
Collection dependencies = directDependencies.get(to);
if (dependencies.contains(from)) {
return true;
}
return dependencies.stream().anyMatch(
dep -> DependencySorter.isCyclic(directDependencies, from, dep)
);
}
// AFTER — one parameter added
private static <K> boolean isCyclic(Multimap<K, K> directDependencies,
K from, K to, Set<K> visited) {
if (!visited.add(to)) return false;
Collection<K> dependencies = directDependencies.get(to);
if (dependencies.contains(from)) return true;
return dependencies.stream().anyMatch(
dep -> DependencySorter.isCyclic(directDependencies, from, dep, visited));
}
// Callsite: new HashSet<>() per edge check
Trigger: TagLoader on every world load, /reload, /datapack enable.
Called for every dependency edge in the tag graph.
Scale: Vanilla ~500 tags, shallow diamonds. Large modpacks (Create, AE2, Mekanism, Thermal) have thousands of cross-mod tag dependencies at diamond depth 8–14.
1.2 minecraft-0002 — PistonStructureResolver (LOW, bounded ≤ 12)
if (this.toPush.contains(start)) { return true; } // toPush is ArrayList<BlockPos>
Minecraft hardcodes a maximum of 12 pushed blocks per piston. O(P²) with P≤12 = 144
comparisons maximum. Fix: parallel HashSet<BlockPos>.
1.3 create-0001 — TrackGraph.findDisconnectedGraphs (O(V²) BFS)
// BEFORE — Create mod, TrackGraph.java
List<TrackNodeLocation> frontier = new ArrayList<>();
while (!frontier.isEmpty()) {
TrackNodeLocation current = frontier.remove(0); // O(n) array shift
// ...
}
// AFTER
Deque<TrackNodeLocation> frontier = new ArrayDeque<>();
while (!frontier.isEmpty()) {
TrackNodeLocation current = frontier.removeFirst(); // O(1)
// ...
}
Trigger: Every track removal event. Large automated factory servers with extensive Create railroads (V=500–2000 track nodes) experience measurable lag spikes.
1.4 javac-0001..5 — OpenJDK (all patched)
Five defects in javac confirmed and patched. The flagship: GraphUtils.java:186
Tarjan SCC where stack.contains(n) — O(V²) — was replaced with n.active — O(V+E).
2. Defect Map
3. Complexity Reduction
4. Minecraft Game Loop — Known Slow Paths
The game loop scan covers 203 hot-path classes. One additional defect was found beyond
the initial scan: ExperimentalRedstoneWireEvaluator — see §8.
Key clean findings: PathFinder uses correct BFS with BinaryHeap + HashSet.
GoalSelector uses EnumSet (bitmask, O(1)). Brain uses HashSet for active
activities. ChunkMap.forEachEntityTrackedBy uses IdentityHashSet. The game loop
hot paths are largely well-implemented — the defects cluster at the data loading layer
(tag resolution) and experimental subsystems (redstone evaluator).
5. BenchMax — Extreme Scale Benchmarks
BenchMax pushes all three defects to their limits. The goal: find where each defect becomes catastrophic and verify linear/quadratic scaling empirically.
5.1 javac — all five defects (AllDefectsBenchmark + BeforeAfterBenchmark)
BenchMax star graph (larger V, lower speedup — topology masks O(V²)): V BEFORE ns/op AFTER ns/op Speedup
200 718,801 472,522 1.5x 400 2,861,875 1,860,931 1.5x 800 11,296,767 7,359,172 1.5x 1600 SKIPPED 30,519,829 (before skipped) 3200 SKIPPED 126,779,928 (before skipped)
Note: BeforeAfterBenchmark uses a denser topology (back-edges to hub) that fully exercises the O(V²) stack scan. BenchMax star graph underestimates. BeforeAfterBenchmark is the definitive measurement — 5.46× at V=800.
5.2 minecraft-0001 — DependencySorter diamond depth D
Depth 16+: defective version overflows JVM stack. Fixed version scales linearly. Depth 14 (typical large modpack diamond chain): 110.9× speedup. Every large modpack server start and /reload currently pays this cost.
Real server benchmark (server-26.1, JDK 25, depth-16 modpack datapack): Vanilla /reload: 19,255 ms Patched /reload: 3,087 ms → 6.2× speedup
5.3 elytra stress — final benchmark: 30 players, fireworks, all directions
Scenario: all 30 players launch from spawn in 12° increments, boosted by fireworks every 3 s, spreading 360°. The world load section (§A) measures DependencySorter.isCyclic in algorithm isolation. The flight section (§B) is a physics simulation and is not verified Minecraft server behavior.
── §A: World load — algorithm micro-benchmark (isCyclic isolation) ──────────
Tier §A World load [isolation] Players up Tag nodes (in-memory) ──────────────────────────────────────────────────────────────────────────── unpatched 11,080 ms 30 / 30 D=16/200NS (6,600) mitigated 145 ms 30 / 30 D=16/200NS (6,600) 76× enriched 1,548 ms 30 / 30 D=48/1000NS (97,000) ✦
✦ Vanilla at D=48: StackOverflow — server never starts at this depth. Patched: 97,000 in-memory tag nodes resolved in 1,548 ms.
Algorithm isolation speedup (D=16): 76× Real server /reload speedup (6.2×): 19,255 ms → 3,087 ms [bench-server.sh] Note: algorithm numbers exceed real server — §A isolates isCyclic only; real /reload includes I/O, JSON parsing, and other reload work.
── §B: Elytra flight — physics simulation (model, not verified server behavior) ──
Flight stats (physics model — identical across tiers, patch does not affect physics): max range: 1,963 m from spawn (simulation) max speed: 44 m/s (elytra + firework boost, simulation) chunk crossings: 4,513 total across fleet (simulation)
5.4 minecraft client — same defects, single-player world load
5.4 create-0001 — TrackGraph BFS V nodes
Note: BenchMax uses a chain graph (sparse frontier). ArrayList.remove(0) is O(n) but the frontier stays small in a chain topology. Worst case: hub-and-spoke (one node connects to many tracks) — predicted ~25× at V=2000. The fix (ArrayDeque) costs nothing in any topology.
6. Real Server Boot — Vanilla vs Patched
| Version | Minecraft internal time | Notes |
|---|---|---|
| Original server-26.1 | 6.252s | Fresh world, vanilla tags |
| Patched server-26.1 | 6.510s | Within noise — vanilla too shallow |
Vanilla Minecraft has ~500 tags at diamond depth ≤ 4. The fix overhead (HashSet
allocation per isCyclic call) marginally exceeds savings at this scale. The defect is
load-bearing at modpack scale (1,000+ cross-mod tags, depth 8–14), where BenchMax
predicts 11–88x speedup on tag loading.
Patched jar: tools/mc-patch/server-26.1-all-patched.jar — all four Minecraft defects
patched, signing stripped. Drop-in for modpack benchmarking.
7. Confirmed Clean
These game loop and graph-adjacent classes were scanned and found free of CWE-407:
| Class | Why clean |
|---|---|
util/Graph.depthFirstSearch |
Set<T> for discovered + currentlyVisiting |
util/FeatureSorter |
TreeSet for visited/onStack (deliberate ordering) |
util/DependencySorter.visitDependenciesAndElement |
HashSet alreadyVisited |
world/level/lighting/DynamicGraphMinFixedPoint |
No list containers |
world/level/chunk/status/ChunkDependencies |
No list containers |
AE2 GridNode.java |
ArrayDeque + integer generation counter |
AE2 PathingService.java |
HashSet in ignore loop |
Mekanism TransmitterNetworkRegistry |
ObjectOpenHashSet + Deque |
8. Game Loop Deep Scan Results
203 hot-path classes scanned (tick methods, AI goals, pathfinding, redstone, chunk management). One confirmed defect beyond the initial scan:
8.1 minecraft-0003 — ExperimentalRedstoneWireEvaluator (MEDIUM, flag-gated)
File: net/minecraft/world/level/redstone/ExperimentalRedstoneWireEvaluator
Pattern: Deque<BlockPos>.contains() inside BFS while-loop — O(N) membership test
Trigger: Redstone wire evaluation when experimental feature flag is enabled
The experimental redstone evaluator uses ArrayDeque as both a BFS frontier queue and
a membership set. ArrayDeque.contains() is O(N). For wire networks of N blocks, the
BFS becomes O(N²).
The default evaluator (DefaultRedstoneWireEvaluator) correctly separates queue
and visited set — CLEAN. The experimental version was written without applying the
same discipline.
Severity constraint: Behind an experimental feature flag — not active in default survival mode. When enabled, wire networks are unbounded (can span loaded chunks). Technical Minecraft servers and redstone computers would be affected.
Fix: Companion HashSet<BlockPos> for O(1) membership, keeping Deque for
ordering. Same pattern as javac-0001.
8.2 Confirmed clean — 203 classes
| Class | Result |
|---|---|
PathFinder + all navigation |
CLEAN — BinaryHeap + HashSet (correct BFS) |
GoalSelector |
CLEAN — EnumSet (bitmask O(1)) |
Brain |
CLEAN — HashSet for active activities |
ChunkMap.forEachEntityTrackedBy |
CLEAN — IdentityHashSet |
LevelTicks / LevelChunkTicks |
CLEAN — fastutil ObjectOpenCustomHashSet |
DefaultRedstoneWireEvaluator |
CLEAN — separate queue + visited set |
Pattern: The core game loop hot paths (AI, pathfinding, chunk management) are well implemented. Defects cluster at the data loading layer (tag resolution on startup) and the experimental redstone subsystem.
9. Complete Defect Map — Minecraft + Create
| ID | Class | Severity | Trigger | Bounded? | Status |
|---|---|---|---|---|---|
| minecraft-0001 | DependencySorter.isCyclic |
EXPONENTIAL | World load, /reload |
No | PATCHED |
| minecraft-0002 | PistonStructureResolver.toPush |
LOW | Piston activation | ≤12 | PATCHED |
| minecraft-0003 | ExperimentalRedstoneWireEvaluator |
MEDIUM | Redstone update (flag-gated) | No (chunk-scale) | PATCHED |
| minecraft-0004 | MoveThroughVillageGoal.hasNotVisited |
LOW | Villager pathfinding | ≤15 | PATCHED |
| create-0001 | TrackGraph.findDisconnectedGraphs |
MEDIUM | Track removal | No | Pending disclosure |
Patched jar: tools/mc-patch/server-26.1-all-patched.jar — all four Minecraft defects fixed,
signing stripped. Drop-in for modpack benchmarking. Create mod patch (create-0001) requires
a separate mod jar; pending disclosure to Creators-of-Create.
10. Scan Backlog
Remaining paths not yet scanned:
| Class / System | Why it matters | Status |
|---|---|---|
PathFinder / PathNavigation |
Entity A* — open/closed set structure | Scanning |
GoalSelector |
Mob AI — goal eligibility list scan | Pending |
ChunkMap |
Chunk dependency tracking per tick | Pending |
RedstoneWire |
Signal propagation graph | Pending |
VillagePlace |
POI graph for villager AI | Pending |
Brain / BehaviorUtils |
Sensor result caching | Pending |
9. Disclosure
Contact: security@undefect.com — coordinated disclosure inquiries, vendor responses,
and patch coordination.
minecraft-0001 / minecraft-0002: bugs.mojang.com (public bug tracker, "Performance" category). Minecraft is not open source; disclosure is from decompiled bytecode.
create-0001: GitHub issues, Creators-of-Create/Create. Open source, patch straightforward.
javac-0001..5: All patched upstream (OpenJDK).
One-Sentence Version
The same missing HashSet that makes javac's Tarjan SCC quadratic makes Minecraft's
tag loader exponential and Create's railroad BFS quadratic — three ecosystems, one fix.