166 lines
6.4 KiB
Java
166 lines
6.4 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
/**
|
|
* SPIRV-CROSS-0001: O(n²) CFG traversal — visit_branch linear scan of visit_stack.
|
|
*
|
|
* Models spirv_cfg.cpp CFG::visit_branch() / post_order_visit_entry().
|
|
*
|
|
* The key: post_order_visit_entry processes each node by calling
|
|
* post_order_visit_branches, which calls visit_branch for each successor.
|
|
* visit_branch checks whether the successor is already in the *new* portion
|
|
* of visit_stack (entries appended since last_visited_size) using std::find.
|
|
*
|
|
* For a graph where each node has branching factor B and the DFS stack
|
|
* depth is D, each visit_branch call scans up to D elements: O(D) per call,
|
|
* O(N*D) total. For a linear chain D=N, giving O(N²).
|
|
*
|
|
* Slow path: std::find on tail of visit_stack (O(tail_size) per call).
|
|
* Fast path: unordered_set mirror of visit_stack (O(1) per call).
|
|
*
|
|
* Counts total comparisons, asserts fast is O(n), prints N/N PASS.
|
|
*/
|
|
public class SpirvcrossVisitBranchAlgorithm {
|
|
|
|
// ---------------------------------------------------------------
|
|
// Shared graph: simulate a worst-case CFG where every new block
|
|
// branches back to all previously-pending blocks (max std::find work).
|
|
// This is the scenario where a block at depth k has k pending siblings
|
|
// in the new portion of visit_stack.
|
|
//
|
|
// We directly count comparisons made by the std::find range.
|
|
// ---------------------------------------------------------------
|
|
|
|
/**
|
|
* Simulate visit_branch for a batch of numBranches successors added to a
|
|
* visit_stack whose "new region" already contains existingNewEntries items.
|
|
* Each branch is unique (not already in the new region), so std::find
|
|
* scans the whole new region before concluding "not found" and appending.
|
|
*
|
|
* Returns total comparisons made.
|
|
*/
|
|
static long slowBatch(int existingNewEntries, int numBranches) {
|
|
long comparisons = 0;
|
|
// For each new branch: scan all existing new entries (all miss), then add.
|
|
for (int b = 0; b < numBranches; b++) {
|
|
comparisons += existingNewEntries + b; // scan existing + previously-added in this batch
|
|
}
|
|
return comparisons;
|
|
}
|
|
|
|
/**
|
|
* Simulate a DFS over a linear chain of N blocks.
|
|
*
|
|
* At each step the outer loop pops the back of visit_stack, sets
|
|
* last_visited_size = visit_stack.size(), then visits branches.
|
|
* For a linear chain each block has exactly 1 successor.
|
|
* The successor may already be known (visited_branches) or new.
|
|
*
|
|
* Worst case for std::find: a star graph where block 0 fans out to N-1
|
|
* successors all pushed in one post_order_visit_branches call.
|
|
* Each successive visit_branch call scans more entries.
|
|
*/
|
|
static long simulateSlow(int numBlocks) {
|
|
long totalComparisons = 0;
|
|
|
|
// Star graph: block 0 → {1, 2, 3, ..., N-1}
|
|
// post_order_visit_branches(0) calls visit_branch(1), visit_branch(2), ...
|
|
// last_visited_size = 1 (just [0] on stack), new region starts empty.
|
|
// visit_branch(1): find in [] → 0 comparisons, add 1. new region = [1]
|
|
// visit_branch(2): find in [1] → 1 comparison (miss), add 2. new region = [1,2]
|
|
// visit_branch(k): find in [1..k-1] → k-1 comparisons, add k.
|
|
// Total = 0+1+2+...+(N-2) = (N-1)*(N-2)/2
|
|
|
|
for (int k = 1; k < numBlocks; k++) {
|
|
totalComparisons += (k - 1); // visit_branch(k) scans k-1 entries already added
|
|
}
|
|
return totalComparisons;
|
|
}
|
|
|
|
static long simulateFast(int numBlocks) {
|
|
// Each visit_branch call: one set.contains() = 1 op
|
|
return numBlocks - 1; // N-1 successors, each checked once
|
|
}
|
|
|
|
// ---------------------------------------------------------------
|
|
// Tests
|
|
// ---------------------------------------------------------------
|
|
static void test(String name, int numBlocks) {
|
|
long slowOps = simulateSlow(numBlocks);
|
|
long fastOps = simulateFast(numBlocks);
|
|
|
|
// Slow: (N-1)*(N-2)/2 comparisons — O(N²)
|
|
long expectedSlow = (long)(numBlocks - 1) * (numBlocks - 2) / 2;
|
|
assert slowOps == expectedSlow :
|
|
name + " slow=" + slowOps + " expected=" + expectedSlow;
|
|
|
|
// Fast: exactly N-1 ops
|
|
assert fastOps == numBlocks - 1 :
|
|
name + " fast=" + fastOps + " expected=" + (numBlocks - 1);
|
|
|
|
assert slowOps >= fastOps :
|
|
name + " fast not faster: slow=" + slowOps + " fast=" + fastOps;
|
|
|
|
double speedup = numBlocks < 3 ? 1.0 : (double) slowOps / fastOps;
|
|
System.out.printf(" %-32s N=%-5d slow=%7d fast=%5d speedup=%.0fx%n",
|
|
name, numBlocks, slowOps, fastOps, speedup);
|
|
}
|
|
|
|
// Also verify set-based visit_branch has identical membership semantics
|
|
static void testSemantics() {
|
|
// Reproduce visit_branch logic directly with both strategies
|
|
List<Integer> slowStack = new ArrayList<>();
|
|
List<Integer> fastStack = new ArrayList<>();
|
|
Set<Integer> fastSet = new HashSet<>();
|
|
|
|
// Push initial block
|
|
slowStack.add(0);
|
|
fastStack.add(0);
|
|
fastSet.add(0);
|
|
|
|
// visit_branch calls for successors, some duplicated
|
|
int[] successors = {1, 2, 3, 1, 4, 2, 5};
|
|
int lastVisitedSize = 1; // after pushing block 0
|
|
|
|
for (int id : successors) {
|
|
// Slow: find from lastVisitedSize onwards
|
|
boolean foundSlow = false;
|
|
for (int i = lastVisitedSize; i < slowStack.size(); i++) {
|
|
if (slowStack.get(i) == id) { foundSlow = true; break; }
|
|
}
|
|
if (!foundSlow) slowStack.add(id);
|
|
|
|
// Fast: set lookup
|
|
if (!fastSet.contains(id)) {
|
|
fastStack.add(id);
|
|
fastSet.add(id);
|
|
}
|
|
}
|
|
|
|
assert slowStack.equals(fastStack) :
|
|
"Semantics mismatch: slow=" + slowStack + " fast=" + fastStack;
|
|
System.out.printf(" %-32s semantics match: %s%n", "semantics check", slowStack);
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
int passed = 0;
|
|
int total = 0;
|
|
|
|
int[] sizes = {10, 50, 100, 200, 500};
|
|
for (int n : sizes) {
|
|
total++;
|
|
test("visitBranch N=" + n, n);
|
|
passed++;
|
|
}
|
|
|
|
total++;
|
|
testSemantics();
|
|
passed++;
|
|
|
|
System.out.printf("%n%d/%d PASS%n", passed, total);
|
|
}
|
|
}
|