java-topology/tests/bench/DependencySorterBenchmark.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

151 lines
6.9 KiB
Java

package bench;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.*;
import java.util.function.Consumer;
/**
* Benchmark for minecraft-0001: DependencySorter.isCyclic exponential defect.
*
* BEFORE: no visited set — O(E^D) on diamond dependency graphs
* AFTER: visited set — O(E) per isCyclic call
*
* Graph topology: diamond chain of depth D
* root → branch_left_d, branch_right_d
* branch_left_d → branch_left_(d-1) → ... → leaf
* branch_right_d → branch_right_(d-1) → ... → leaf
* (shared leaf causes exponential revisiting without visited set)
*
* In Minecraft: this is the tag dependency structure of modpack tag inheritance.
* Tags like #c:ingots depended upon by both #create:ingots and #ae2:ingots
* form exactly this diamond pattern, with depth proportional to mod count.
*/
public class DependencySorterBenchmark {
static final int WARMUP = 500;
static final int TRIALS = 2000;
// ── Simple tag entry for benchmarking ─────────────────────────────────────
record TagEntry(List<String> required) implements SorterEntry {
public void visitRequiredDependencies(Consumer<String> c) { required.forEach(c); }
public void visitOptionalDependencies(Consumer<String> c) {}
}
interface SorterEntry {
void visitRequiredDependencies(Consumer<String> c);
void visitOptionalDependencies(Consumer<String> c);
}
// ── Build diamond dependency graph of depth D ─────────────────────────────
// Structure: root → L1, R1; L1 → L2, R2; ... but all converge on shared leaf
// Creates 2^D paths from root to the shared base tag
static Map<String, List<String>> buildDiamondTags(int depth) {
Map<String, List<String>> deps = new LinkedHashMap<>();
// shared base at bottom
deps.put("base", List.of());
// each level has left and right that both depend on the level below
String prevLeft = "base", prevRight = "base";
for (int d = 1; d <= depth; d++) {
String left = "left_" + d;
String right = "right_" + d;
deps.put(left, List.of(prevLeft, prevRight));
deps.put(right, List.of(prevLeft, prevRight));
prevLeft = left;
prevRight = right;
}
deps.put("root", List.of(prevLeft, prevRight));
return deps;
}
// ── BEFORE: isCyclic with no visited set ──────────────────────────────────
static boolean isCyclicBefore(Multimap<String, String> deps, String from, String to) {
Collection<String> d = deps.get(to);
if (d.contains(from)) return true;
return d.stream().anyMatch(dep -> isCyclicBefore(deps, from, dep));
}
static void addDepBefore(Multimap<String, String> deps, String from, String to) {
if (!isCyclicBefore(deps, from, to)) deps.put(from, to);
}
static long runBefore(Map<String, List<String>> tagDeps) {
HashMultimap<String, String> deps = HashMultimap.create();
for (Map.Entry<String, List<String>> e : tagDeps.entrySet())
for (String dep : e.getValue())
addDepBefore(deps, e.getKey(), dep);
return deps.size();
}
// ── AFTER: isCyclic with visited set ──────────────────────────────────────
static boolean isCyclicAfter(Multimap<String, String> deps, String from, String to, Set<String> visited) {
if (!visited.add(to)) return false;
Collection<String> d = deps.get(to);
if (d.contains(from)) return true;
return d.stream().anyMatch(dep -> isCyclicAfter(deps, from, dep, visited));
}
static void addDepAfter(Multimap<String, String> deps, String from, String to) {
if (!isCyclicAfter(deps, from, to, new HashSet<>())) deps.put(from, to);
}
static long runAfter(Map<String, List<String>> tagDeps) {
HashMultimap<String, String> deps = HashMultimap.create();
for (Map.Entry<String, List<String>> e : tagDeps.entrySet())
for (String dep : e.getValue())
addDepAfter(deps, e.getKey(), dep);
return deps.size();
}
// ── Main ──────────────────────────────────────────────────────────────────
public static void main(String[] args) {
System.out.println("=== minecraft-0001: DependencySorter.isCyclic Benchmark ===");
System.out.println("Graph: diamond dependency chain (depth D)");
System.out.println("Each level doubles the paths to the shared base tag.");
System.out.println("Models modpack tag inheritance (e.g. #c:ingots diamond patterns).");
System.out.println();
System.out.printf("%-6s %-8s %-14s %-14s %-10s%n",
"Depth", "Tags", "BEFORE ns", "AFTER ns", "Speedup");
System.out.println("-".repeat(60));
int[] depths = {2, 4, 6, 8, 10, 12, 14, 16};
for (int d : depths) {
Map<String, List<String>> tags = buildDiamondTags(d);
int tagCount = tags.size();
// warmup — skip deep depths for before (stack overflow risk)
if (d <= 14) {
for (int i = 0; i < WARMUP; i++) runBefore(tags);
}
for (int i = 0; i < WARMUP; i++) runAfter(tags);
// time BEFORE (skip if too deep — stack overflow from exponential recursion)
long beforeNs = -1;
if (d <= 14) {
long t0 = System.nanoTime();
for (int i = 0; i < TRIALS; i++) runBefore(tags);
beforeNs = (System.nanoTime() - t0) / TRIALS;
}
// time AFTER
long t0 = System.nanoTime();
for (int i = 0; i < TRIALS; i++) runAfter(tags);
long afterNs = (System.nanoTime() - t0) / TRIALS;
if (beforeNs >= 0) {
double speedup = (double) beforeNs / afterNs;
System.out.printf("%-6d %-8d %-14d %-14d %.1fx%n",
d, tagCount, beforeNs, afterNs, speedup);
} else {
System.out.printf("%-6d %-8d %-14s %-14d (overflow)%n",
d, tagCount, "OVERFLOW", afterNs);
}
}
System.out.println();
System.out.println("Note: depth 16+ causes stack overflow BEFORE the fix — the");
System.out.println("recursion tree is 2^16 = 65,536 nodes deep with no visited set.");
System.out.println("A typical large modpack (Create + AE2 + Mekanism + Thermal) has");
System.out.println("diamond chains of depth 8-12 across thousands of cross-mod tags.");
}
}