java-topology/defects/jsc/unit/JSCDFGGraphPredecessorTest.java

200 lines
6.8 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* JSC-0002: DFGGraph::handleSuccessor() O(E×P) predecessor dedup via Vector::contains.
*
* Models DFGGraph.cpp lines 738-746:
* - SlowGraph: predecessor dedup using List::contains — O(P) per edge
* - FastGraph: predecessor dedup using HashSet — O(1) per edge
*
* Compile: javac -d . *.java (from defects/jsc/unit/)
* Run: java -ea unit.JSCDFGGraphPredecessorTest
*/
public class JSCDFGGraphPredecessorTest {
static class Block {
final int id;
boolean isReachable = false;
final List<Integer> successors;
Block(int id, List<Integer> successors) {
this.id = id;
this.successors = successors;
}
}
// ---- slow path: List::contains O(P) per edge ----------------------------
static class SlowGraph {
static long opCount;
final List<Block> blocks;
final List<List<Integer>> predecessors; // per block
SlowGraph(List<Block> blocks) {
this.blocks = blocks;
predecessors = new ArrayList<>();
for (int i = 0; i < blocks.size(); i++)
predecessors.add(new ArrayList<>());
}
void handleSuccessor(Queue<Integer> worklist, int blockId, int succId) {
Block succ = blocks.get(succId);
if (!succ.isReachable) {
succ.isReachable = true;
worklist.add(succId);
}
List<Integer> preds = predecessors.get(succId);
// O(P) linear scan — the defect
boolean found = false;
for (int pred : preds) {
opCount++;
if (pred == blockId) { found = true; break; }
}
if (!found) preds.add(blockId);
}
void determineReachability() {
Queue<Integer> worklist = new LinkedList<>();
blocks.get(0).isReachable = true;
worklist.add(0);
while (!worklist.isEmpty()) {
int blockId = worklist.poll();
for (int succ : blocks.get(blockId).successors)
handleSuccessor(worklist, blockId, succ);
}
}
}
// ---- fast path: HashSet O(1) dedup --------------------------------------
static class FastGraph {
static long opCount;
final List<Block> blocks;
final List<List<Integer>> predecessors;
final List<HashSet<Integer>> predSeen; // dedup set
FastGraph(List<Block> blocks) {
this.blocks = blocks;
predecessors = new ArrayList<>();
predSeen = new ArrayList<>();
for (int i = 0; i < blocks.size(); i++) {
predecessors.add(new ArrayList<>());
predSeen.add(new HashSet<>());
}
}
void handleSuccessor(Queue<Integer> worklist, int blockId, int succId) {
Block succ = blocks.get(succId);
if (!succ.isReachable) {
succ.isReachable = true;
worklist.add(succId);
}
opCount++; // one hash lookup+insert
if (predSeen.get(succId).add(blockId)) {
predecessors.get(succId).add(blockId);
}
}
void determineReachability() {
Queue<Integer> worklist = new LinkedList<>();
blocks.get(0).isReachable = true;
worklist.add(0);
while (!worklist.isEmpty()) {
int blockId = worklist.poll();
for (int succ : blocks.get(blockId).successors)
handleSuccessor(worklist, blockId, succ);
}
}
}
// ---- graph builder: switch with N arms all targeting block N+1 ----------
// Block 0: entry, edges to blocks 1..N
// Blocks 1..N: arms, each edges to block N+1
// Block N+1: join/merge block
static List<Block> buildSwitchGraph(int N) {
List<Block> blocks = new ArrayList<>();
// block 0: switch, targets 1..N
List<Integer> arms = new ArrayList<>();
for (int i = 1; i <= N; i++) arms.add(i);
blocks.add(new Block(0, arms));
// blocks 1..N: each targets join block N+1
for (int i = 1; i <= N; i++) {
List<Integer> succ = new ArrayList<>();
succ.add(N + 1);
blocks.add(new Block(i, succ));
}
// block N+1: join (no successors)
blocks.add(new Block(N + 1, new ArrayList<>()));
return blocks;
}
// ---- tests --------------------------------------------------------------
static int passed = 0;
static int total = 0;
static void check(String name, boolean cond) {
total++;
if (cond) {
passed++;
} else {
System.out.println("FAIL: " + name);
}
}
public static void main(String[] args) {
// correctness: same predecessor list for small switch
{
int N = 5;
List<Block> slowBlocks = buildSwitchGraph(N);
List<Block> fastBlocks = buildSwitchGraph(N);
SlowGraph slow = new SlowGraph(slowBlocks);
FastGraph fast = new FastGraph(fastBlocks);
slow.determineReachability();
fast.determineReachability();
// join block (N+1) should have exactly N predecessors
check("slow: join preds == N", slow.predecessors.get(N + 1).size() == N);
check("fast: join preds == N", fast.predecessors.get(N + 1).size() == N);
}
// op-count scaling
int[] switchSizes = {10, 50, 100, 200, 500};
System.out.println();
System.out.printf("%-8s %12s %12s %8s%n", "N_arms", "slow_ops", "fast_ops", "ratio");
for (int N : switchSizes) {
List<Block> slowBlocks = buildSwitchGraph(N);
List<Block> fastBlocks = buildSwitchGraph(N);
SlowGraph.opCount = 0;
FastGraph.opCount = 0;
new SlowGraph(slowBlocks).determineReachability();
new FastGraph(fastBlocks).determineReachability();
long slow = SlowGraph.opCount;
long fast = FastGraph.opCount;
double ratio = (double) slow / fast;
System.out.printf("%-8d %12d %12d %8.1f%n", N, slow, fast, ratio);
check("slow > fast for N=" + N, slow > fast);
// slow O(N²) for the join block: sum 0..N-1 ~ N²/2
// fast O(N): exactly N lookups for join block
// ratio should grow with N; actual ratio ≈ N/2 - small constant
if (N >= 50) {
check("ratio >= N/5 for N=" + N, ratio >= (double) N / 5);
}
}
System.out.println();
System.out.printf("%d/%d PASS%n", passed, total);
if (passed != total) System.exit(1);
}
}