200 lines
6.8 KiB
Java
200 lines
6.8 KiB
Java
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);
|
||
}
|
||
}
|