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.
268 lines
11 KiB
Java
268 lines
11 KiB
Java
package bench;
|
|
|
|
import com.google.common.collect.HashMultimap;
|
|
import com.google.common.collect.Multimap;
|
|
import java.util.*;
|
|
import java.util.function.Consumer;
|
|
|
|
/**
|
|
* BenchMax — extreme-scale benchmarks for the gumyum white paper.
|
|
*
|
|
* Three defects, pushed to their limits:
|
|
*
|
|
* 1. javac Tarjan (starWithBackEdges) — V up to 5000
|
|
* BEFORE: stack.contains(n) O(V²)
|
|
* AFTER: n.active flag O(V+E)
|
|
*
|
|
* 2. minecraft-0001 DependencySorter (diamond depth) — D up to 18
|
|
* BEFORE: isCyclic no visited set O(E^D)
|
|
* AFTER: visited set O(E) per call
|
|
*
|
|
* 3. create-0001 TrackGraph BFS (ArrayList.remove(0)) — V up to 5000
|
|
* BEFORE: ArrayList.remove(0) O(V²) BFS
|
|
* AFTER: ArrayDeque.poll() O(V+E)
|
|
*/
|
|
public class BenchMax {
|
|
|
|
// ── 1. Tarjan — javac-style ───────────────────────────────────────────────
|
|
|
|
static class TNode {
|
|
final int id;
|
|
final List<TNode> neighbors = new ArrayList<>();
|
|
boolean active = false; // fix flag
|
|
|
|
TNode(int id) { this.id = id; }
|
|
}
|
|
|
|
static List<TNode> starWithBackEdges(int v) {
|
|
List<TNode> nodes = new ArrayList<>();
|
|
for (int i = 0; i < v; i++) nodes.add(new TNode(i));
|
|
TNode hub = nodes.get(0);
|
|
for (int i = 1; i < v; i++) {
|
|
hub.neighbors.add(nodes.get(i));
|
|
nodes.get(i).neighbors.add(hub); // back edge
|
|
}
|
|
return nodes;
|
|
}
|
|
|
|
static void tarjanDefective(List<TNode> graph) {
|
|
List<TNode> stack = new ArrayList<>();
|
|
for (TNode n : graph) {
|
|
if (!stack.contains(n)) tarjanDfsDefective(n, stack);
|
|
}
|
|
}
|
|
|
|
static void tarjanDfsDefective(TNode n, List<TNode> stack) {
|
|
stack.add(n);
|
|
for (TNode w : n.neighbors) {
|
|
if (stack.contains(w)) continue; // O(n) — defect
|
|
tarjanDfsDefective(w, stack);
|
|
}
|
|
stack.remove(stack.size() - 1);
|
|
}
|
|
|
|
static void tarjanFixed(List<TNode> graph) {
|
|
Deque<TNode> stack = new ArrayDeque<>();
|
|
for (TNode n : graph) {
|
|
if (!n.active) tarjanDfsFixed(n, stack);
|
|
}
|
|
}
|
|
|
|
static void tarjanDfsFixed(TNode n, Deque<TNode> stack) {
|
|
n.active = true;
|
|
stack.push(n);
|
|
for (TNode w : n.neighbors) {
|
|
if (w.active) continue; // O(1) — fixed
|
|
tarjanDfsFixed(w, stack);
|
|
}
|
|
stack.pop();
|
|
n.active = false;
|
|
}
|
|
|
|
// ── 2. DependencySorter — minecraft-0001 ─────────────────────────────────
|
|
|
|
static Map<String, List<String>> buildDiamond(int depth) {
|
|
Map<String, List<String>> deps = new LinkedHashMap<>();
|
|
deps.put("base", List.of());
|
|
String prevL = "base", prevR = "base";
|
|
for (int d = 1; d <= depth; d++) {
|
|
String L = "L" + d, R = "R" + d;
|
|
deps.put(L, List.of(prevL, prevR));
|
|
deps.put(R, List.of(prevL, prevR));
|
|
prevL = L; prevR = R;
|
|
}
|
|
deps.put("root", List.of(prevL, prevR));
|
|
return deps;
|
|
}
|
|
|
|
static boolean isCyclicBefore(Multimap<String,String> d, String from, String to) {
|
|
Collection<String> deps = d.get(to);
|
|
if (deps.contains(from)) return true;
|
|
return deps.stream().anyMatch(dep -> isCyclicBefore(d, from, dep));
|
|
}
|
|
|
|
static boolean isCyclicAfter(Multimap<String,String> d, String from, String to, Set<String> vis) {
|
|
if (!vis.add(to)) return false;
|
|
Collection<String> deps = d.get(to);
|
|
if (deps.contains(from)) return true;
|
|
return deps.stream().anyMatch(dep -> isCyclicAfter(d, from, dep, vis));
|
|
}
|
|
|
|
static void runDSBefore(Map<String, List<String>> tags) {
|
|
HashMultimap<String,String> d = HashMultimap.create();
|
|
for (var e : tags.entrySet())
|
|
for (String dep : e.getValue())
|
|
if (!isCyclicBefore(d, e.getKey(), dep)) d.put(e.getKey(), dep);
|
|
}
|
|
|
|
static void runDSAfter(Map<String, List<String>> tags) {
|
|
HashMultimap<String,String> d = HashMultimap.create();
|
|
for (var e : tags.entrySet())
|
|
for (String dep : e.getValue())
|
|
if (!isCyclicAfter(d, e.getKey(), dep, new HashSet<>())) d.put(e.getKey(), dep);
|
|
}
|
|
|
|
// ── 3. TrackGraph BFS — create-0001 ──────────────────────────────────────
|
|
|
|
// Build a simple chain graph: 0→1→2→...→(v-1) with back edges every 10 nodes
|
|
static Map<Integer, List<Integer>> buildRailGraph(int v) {
|
|
Map<Integer, List<Integer>> adj = new HashMap<>();
|
|
for (int i = 0; i < v; i++) {
|
|
adj.put(i, new ArrayList<>());
|
|
if (i > 0) { adj.get(i).add(i-1); adj.get(i-1).add(i); }
|
|
if (i > 10) { adj.get(i).add(i-10); adj.get(i-10).add(i); }
|
|
}
|
|
return adj;
|
|
}
|
|
|
|
// BEFORE: ArrayList as BFS frontier — remove(0) is O(n)
|
|
static int bfsBefore(Map<Integer, List<Integer>> adj, int start) {
|
|
List<Integer> frontier = new ArrayList<>();
|
|
Set<Integer> visited = new HashSet<>();
|
|
frontier.add(start);
|
|
visited.add(start);
|
|
int count = 0;
|
|
while (!frontier.isEmpty()) {
|
|
int cur = frontier.remove(0); // O(n) — defect
|
|
count++;
|
|
for (int nb : adj.getOrDefault(cur, List.of())) {
|
|
if (visited.add(nb)) frontier.add(nb);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// AFTER: ArrayDeque as BFS frontier — poll() is O(1)
|
|
static int bfsAfter(Map<Integer, List<Integer>> adj, int start) {
|
|
Deque<Integer> frontier = new ArrayDeque<>();
|
|
Set<Integer> visited = new HashSet<>();
|
|
frontier.add(start);
|
|
visited.add(start);
|
|
int count = 0;
|
|
while (!frontier.isEmpty()) {
|
|
int cur = frontier.poll(); // O(1) — fixed
|
|
count++;
|
|
for (int nb : adj.getOrDefault(cur, List.of())) {
|
|
if (visited.add(nb)) frontier.add(nb);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
// ── Main ─────────────────────────────────────────────────────────────────
|
|
|
|
static final int WARMUP = 200;
|
|
static final int TRIALS = 1000;
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("╔══════════════════════════════════════════════════════════════╗");
|
|
System.out.println("║ BenchMax — gumyum minecraft enterprise ║");
|
|
System.out.println("╚══════════════════════════════════════════════════════════════╝");
|
|
System.out.println();
|
|
|
|
// ── Javac Tarjan ─────────────────────────────────────────────────────
|
|
System.out.println("── 1. javac Tarjan starWithBackEdges(V) ──────────────────────");
|
|
System.out.printf("%-6s %-14s %-14s %-8s%n", "V", "BEFORE ns/op", "AFTER ns/op", "Speedup");
|
|
System.out.println("-".repeat(48));
|
|
|
|
int[] tvSizes = {200, 400, 800, 1600, 3200};
|
|
for (int v : tvSizes) {
|
|
List<TNode> g = starWithBackEdges(v);
|
|
for (int i = 0; i < WARMUP; i++) { tarjanFixed(g); }
|
|
// skip large V for before (stack depth)
|
|
long beforeNs = -1;
|
|
if (v <= 800) {
|
|
for (int i = 0; i < WARMUP; i++) tarjanDefective(g);
|
|
long t0 = System.nanoTime();
|
|
for (int i = 0; i < TRIALS; i++) tarjanDefective(starWithBackEdges(v));
|
|
beforeNs = (System.nanoTime() - t0) / TRIALS;
|
|
}
|
|
long t0 = System.nanoTime();
|
|
for (int i = 0; i < TRIALS; i++) tarjanFixed(starWithBackEdges(v));
|
|
long afterNs = (System.nanoTime() - t0) / TRIALS;
|
|
|
|
if (beforeNs >= 0) {
|
|
System.out.printf("%-6d %-14d %-14d %.1fx%n", v, beforeNs, afterNs, (double)beforeNs/afterNs);
|
|
} else {
|
|
System.out.printf("%-6d %-14s %-14d (before too slow)%n", v, "SKIPPED", afterNs);
|
|
}
|
|
}
|
|
|
|
// ── Minecraft DependencySorter ────────────────────────────────────────
|
|
System.out.println();
|
|
System.out.println("── 2. minecraft-0001 DependencySorter diamond depth D ───────");
|
|
System.out.printf("%-6s %-8s %-14s %-14s %-8s%n", "Depth", "Tags", "BEFORE ns", "AFTER ns", "Speedup");
|
|
System.out.println("-".repeat(60));
|
|
|
|
int[] depths = {4, 6, 8, 10, 12, 14, 16, 18};
|
|
for (int d : depths) {
|
|
Map<String, List<String>> tags = buildDiamond(d);
|
|
for (int i = 0; i < WARMUP; i++) runDSAfter(tags);
|
|
|
|
long beforeNs = -1;
|
|
if (d <= 14) {
|
|
for (int i = 0; i < WARMUP; i++) runDSBefore(tags);
|
|
long t0 = System.nanoTime();
|
|
for (int i = 0; i < TRIALS; i++) runDSBefore(tags);
|
|
beforeNs = (System.nanoTime() - t0) / TRIALS;
|
|
}
|
|
long t0 = System.nanoTime();
|
|
for (int i = 0; i < TRIALS; i++) runDSAfter(tags);
|
|
long afterNs = (System.nanoTime() - t0) / TRIALS;
|
|
|
|
if (beforeNs >= 0) {
|
|
System.out.printf("%-6d %-8d %-14d %-14d %.1fx%n",
|
|
d, tags.size(), beforeNs, afterNs, (double)beforeNs/afterNs);
|
|
} else {
|
|
System.out.printf("%-6d %-8d %-14s %-14d (OVERFLOW)%n",
|
|
d, tags.size(), "OVERFLOW", afterNs);
|
|
}
|
|
}
|
|
|
|
// ── Create TrackGraph BFS ─────────────────────────────────────────────
|
|
System.out.println();
|
|
System.out.println("── 3. create-0001 TrackGraph BFS V nodes ────────────────────");
|
|
System.out.printf("%-6s %-14s %-14s %-8s%n", "V", "BEFORE ns/op", "AFTER ns/op", "Speedup");
|
|
System.out.println("-".repeat(48));
|
|
|
|
int[] bfsSizes = {100, 500, 1000, 2000, 5000};
|
|
for (int v : bfsSizes) {
|
|
Map<Integer, List<Integer>> adj = buildRailGraph(v);
|
|
for (int i = 0; i < WARMUP; i++) { bfsBefore(adj, 0); bfsAfter(adj, 0); }
|
|
|
|
long t0 = System.nanoTime();
|
|
for (int i = 0; i < TRIALS; i++) bfsBefore(adj, 0);
|
|
long beforeNs = (System.nanoTime() - t0) / TRIALS;
|
|
|
|
t0 = System.nanoTime();
|
|
for (int i = 0; i < TRIALS; i++) bfsAfter(adj, 0);
|
|
long afterNs = (System.nanoTime() - t0) / TRIALS;
|
|
|
|
System.out.printf("%-6d %-14d %-14d %.1fx%n", v, beforeNs, afterNs, (double)beforeNs/afterNs);
|
|
}
|
|
|
|
System.out.println();
|
|
System.out.println("Platform: " + System.getProperty("java.vm.name") +
|
|
" " + System.getProperty("java.version"));
|
|
}
|
|
}
|