java-topology/defects/javac/unit/InterfaceCandidatesAlgorithm.java

152 lines
6 KiB
Java

package unit;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
/**
* Models Types.interfaceCandidates() dedup logic — defective vs fixed.
*
* DEFECT (javac-0006): when collecting interface method candidates from a
* membersClosure walk, duplicates are filtered with List.contains() which
* is O(S) on the javac singly-linked List. As S candidates accumulate the
* S-th insertion requires scanning S-1 prior entries → O(S²) total.
*
* FIX: maintain a parallel LinkedHashSet<T> for O(1) dedup while still
* building the prepend-ordered List for callers.
*
* Test topology — diamond interface symbol stream:
* Simulates membersClosure returning S unique symbols followed by S-1
* duplicates (a realistic diamond hierarchy where each interface method
* is inherited through two paths and therefore appears twice in the
* closure walk).
*
* SLOW: 1 + 2 + ... + (S-1) ≈ S²/2 contains scans (duplicates hit the
* entire existing list before being rejected).
* FAST: S add-to-HashSet calls, O(1) each.
*/
public class InterfaceCandidatesAlgorithm {
// ── Result ───────────────────────────────────────────────────────────────
public static class Result {
public final List<String> candidates;
public final long ops;
public Result(List<String> candidates, long ops) {
this.candidates = candidates;
this.ops = ops;
}
}
// ── Defective ─────────────────────────────────────────────────────────
/**
* Mirrors: List<MethodSymbol> candidates2 = List.nil();
* for (Symbol s : closureSymbols) {
* if (!candidates2.contains(s)) {
* candidates2 = candidates2.prepend(s);
* }
* }
*
* Uses ArrayList to simulate the linear-scan javac List.contains() cost.
*/
public static Result slow(List<String> symbols) {
long[] ops = {0};
// simulate javac List<MethodSymbol> with an ArrayList for linear scan
ArrayList<String> candidatesList = new ArrayList<>();
for (String sym : symbols) {
// linear scan — each element of candidatesList is one "op"
boolean found = false;
for (String existing : candidatesList) {
ops[0]++;
if (existing.equals(sym)) {
found = true;
break;
}
}
if (!found) {
candidatesList.add(0, sym); // prepend
}
}
return new Result(new ArrayList<>(candidatesList), ops[0]);
}
// ── Fixed ─────────────────────────────────────────────────────────────
/**
* Mirrors: LinkedHashSet<MethodSymbol> seen = new LinkedHashSet<>();
* List<MethodSymbol> candidates2 = List.nil();
* for (Symbol s : closureSymbols) {
* if (seen.add(s)) {
* candidates2 = candidates2.prepend(s);
* }
* }
*
* O(1) HashSet add for dedup; maintains List for ordering contract.
*/
public static Result fast(List<String> symbols) {
long[] ops = {0};
LinkedHashSet<String> seen = new LinkedHashSet<>();
ArrayList<String> candidatesList = new ArrayList<>();
for (String sym : symbols) {
ops[0]++; // one hash op
if (seen.add(sym)) {
candidatesList.add(0, sym); // prepend
}
}
return new Result(new ArrayList<>(candidatesList), ops[0]);
}
// ── Test harness ─────────────────────────────────────────────────────
/**
* Build a diamond-hierarchy symbol stream:
* S unique symbols followed by S-1 of those same symbols again
* (simulating two inheritance paths resolving the same methods).
*/
static List<String> diamondSymbols(int s) {
List<String> syms = new ArrayList<>(2 * s - 1);
for (int i = 0; i < s; i++) {
syms.add("method_" + i);
}
// duplicate path: re-emit first S-1 symbols (the "other parent")
for (int i = 0; i < s - 1; i++) {
syms.add("method_" + i);
}
return syms;
}
public static void main(String[] args) {
int[] sizes = {100, 200, 400};
int passes = 0;
int fails = 0;
for (int s : sizes) {
List<String> syms = diamondSymbols(s);
Result slow = slow(syms);
Result fast = fast(syms);
// correctness: both must produce the same candidate set
LinkedHashSet<String> slowSet = new LinkedHashSet<>(slow.candidates);
LinkedHashSet<String> fastSet = new LinkedHashSet<>(fast.candidates);
boolean correct = slowSet.equals(fastSet) && slow.candidates.size() == s;
// ratio: slow ops should be >> fast ops
double ratio = (double) slow.ops / fast.ops;
boolean ratioOk = ratio >= 5.0;
if (correct && ratioOk) {
System.out.printf("PASS S=%4d slow=%8d fast=%6d ratio=%6.1fx%n",
s, slow.ops, fast.ops, ratio);
passes++;
} else {
System.out.printf("FAIL S=%4d correct=%b ratio=%.1fx (need>=5)%n",
s, correct, ratio);
fails++;
}
}
System.out.printf("%n%d/%d PASS%n", passes, passes + fails);
if (fails > 0) System.exit(1);
}
}