152 lines
6 KiB
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);
|
|
}
|
|
}
|