208 lines
8 KiB
Java
208 lines
8 KiB
Java
package unit;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.LinkedHashMap;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* Models InferenceContext.notifyChange() diff-hoist defect — defective vs fixed.
|
||
*
|
||
* DEFECT (javac-0007): inferencevars.diff(inferredVars) is recomputed on
|
||
* every iteration of the freeTypeListeners loop.
|
||
*
|
||
* void notifyChange(List<Type> inferredVars) {
|
||
* for (entry : freeTypeListeners.entrySet()) {
|
||
* if (!Type.containsAny(entry.getValue(),
|
||
* inferencevars.diff(inferredVars))) { // ← rebuilt L times
|
||
* ...
|
||
* }
|
||
* }
|
||
* }
|
||
*
|
||
* List.diff() iterates inferredVars (M elements) and for each scans
|
||
* inferencevars (N elements) → O(N×M) per call. Called L times: O(L×N×M).
|
||
*
|
||
* FIX: hoist the diff() before the loop.
|
||
*
|
||
* List<Type> remainingVars = inferencevars.diff(inferredVars); // once
|
||
* for (entry : freeTypeListeners.entrySet()) {
|
||
* if (!Type.containsAny(entry.getValue(), remainingVars)) {
|
||
* ...
|
||
* }
|
||
* }
|
||
*
|
||
* Complexity:
|
||
* Defective: O(L × N × M)
|
||
* Fixed: O(N×M + L×V)
|
||
*
|
||
* Test topology:
|
||
* N = inferencevars (type-variable list length)
|
||
* M = inferredVars (resolved subset — set to N/2 for worst-case diff work)
|
||
* L = listener count
|
||
*
|
||
* The op-counter measures diff() recomputation cost:
|
||
* SLOW: diff() called L times → L × N × M ops
|
||
* FAST: diff() called once → N × M ops (then L cheap list-scan checks)
|
||
*/
|
||
public class NotifyChangeAlgorithm {
|
||
|
||
// ── Result ───────────────────────────────────────────────────────────────
|
||
|
||
public static class Result {
|
||
public final int notifiedCount; // how many listeners fired
|
||
public final long ops;
|
||
public Result(int notifiedCount, long ops) {
|
||
this.notifiedCount = notifiedCount;
|
||
this.ops = ops;
|
||
}
|
||
}
|
||
|
||
// ── helpers ──────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* Simulates List.diff(that): iterates every element of 'from' (N items),
|
||
* for each does a linear scan of 'that' (M items). Returns elements of
|
||
* 'from' not present in 'that'.
|
||
*/
|
||
static List<String> diff(List<String> from, List<String> that, long[] ops) {
|
||
List<String> result = new ArrayList<>();
|
||
for (String f : from) {
|
||
boolean found = false;
|
||
for (String t : that) {
|
||
ops[0]++;
|
||
if (f.equals(t)) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) result.add(f);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* Simulates Type.containsAny(ts1, ts2): returns true if any element of
|
||
* ts1 is present in ts2.
|
||
*/
|
||
static boolean containsAny(List<String> ts1, List<String> ts2) {
|
||
for (String t : ts1) {
|
||
for (String s : ts2) {
|
||
if (t.equals(s)) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// ── Defective ─────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* diff() recomputed inside every listener iteration.
|
||
*/
|
||
public static Result slow(List<String> inferencevars,
|
||
List<String> inferredVars,
|
||
Map<String, List<String>> listeners) {
|
||
long[] ops = {0};
|
||
int notified = 0;
|
||
for (Map.Entry<String, List<String>> entry : listeners.entrySet()) {
|
||
// diff recomputed every iteration
|
||
List<String> remaining = diff(inferencevars, inferredVars, ops);
|
||
if (!containsAny(entry.getValue(), remaining)) {
|
||
notified++;
|
||
}
|
||
}
|
||
return new Result(notified, ops[0]);
|
||
}
|
||
|
||
// ── Fixed ─────────────────────────────────────────────────────────────
|
||
|
||
/**
|
||
* diff() hoisted before the loop.
|
||
*/
|
||
public static Result fast(List<String> inferencevars,
|
||
List<String> inferredVars,
|
||
Map<String, List<String>> listeners) {
|
||
long[] ops = {0};
|
||
int notified = 0;
|
||
List<String> remaining = diff(inferencevars, inferredVars, ops); // once
|
||
for (Map.Entry<String, List<String>> entry : listeners.entrySet()) {
|
||
if (!containsAny(entry.getValue(), remaining)) {
|
||
notified++;
|
||
}
|
||
}
|
||
return new Result(notified, ops[0]);
|
||
}
|
||
|
||
// ── Test harness ─────────────────────────────────────────────────────
|
||
|
||
static List<String> makeVars(String prefix, int n) {
|
||
List<String> vars = new ArrayList<>(n);
|
||
for (int i = 0; i < n; i++) vars.add(prefix + i);
|
||
return vars;
|
||
}
|
||
|
||
/**
|
||
* Build listener map: L listeners each watching V variables from
|
||
* inferencevars. We pick variables that are NOT in inferredVars so
|
||
* that containsAny returns false (listener fires).
|
||
*/
|
||
static Map<String, List<String>> makeListeners(List<String> inferencevars,
|
||
List<String> inferredVars,
|
||
int l, int v) {
|
||
// build set of "remaining" vars (not inferred) to register listeners on
|
||
List<String> remaining = new ArrayList<>();
|
||
for (String iv : inferencevars) {
|
||
if (!inferredVars.contains(iv)) remaining.add(iv);
|
||
}
|
||
Map<String, List<String>> map = new LinkedHashMap<>();
|
||
for (int i = 0; i < l; i++) {
|
||
List<String> watched = new ArrayList<>();
|
||
// each listener watches v vars from remaining (cycling)
|
||
for (int j = 0; j < v; j++) {
|
||
watched.add(remaining.get((i + j) % remaining.size()));
|
||
}
|
||
map.put("listener_" + i, watched);
|
||
}
|
||
return map;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
// N=inferencevars, M=inferredVars (=N/2), L=listeners
|
||
int[][] configs = {
|
||
{40, 20, 40}, // N=40, M=20, L=40
|
||
{80, 40, 80}, // N=80, M=40, L=80
|
||
{160, 80, 160}, // N=160, M=80, L=160
|
||
};
|
||
int passes = 0;
|
||
int fails = 0;
|
||
|
||
for (int[] c : configs) {
|
||
int n = c[0], m = c[1], l = c[2];
|
||
List<String> inferencevars = makeVars("T", n);
|
||
List<String> inferredVars = makeVars("T", m); // T0..T(m-1) inferred
|
||
Map<String, List<String>> listeners = makeListeners(inferencevars, inferredVars, l, 3);
|
||
|
||
Result slow = slow(inferencevars, inferredVars, listeners);
|
||
Result fast = fast(inferencevars, inferredVars, listeners);
|
||
|
||
// correctness: same notification count
|
||
boolean correct = slow.notifiedCount == fast.notifiedCount;
|
||
// ratio: slow should be >> fast
|
||
double ratio = (double) slow.ops / Math.max(fast.ops, 1);
|
||
boolean ratioOk = ratio >= 5.0;
|
||
|
||
if (correct && ratioOk) {
|
||
System.out.printf("PASS N=%4d M=%4d L=%4d slow=%10d fast=%8d ratio=%6.1fx%n",
|
||
n, m, l, slow.ops, fast.ops, ratio);
|
||
passes++;
|
||
} else {
|
||
System.out.printf("FAIL N=%4d M=%4d L=%4d correct=%b ratio=%.1fx (need>=5)%n",
|
||
n, m, l, correct, ratio);
|
||
fails++;
|
||
}
|
||
}
|
||
|
||
System.out.printf("%n%d/%d PASS%n", passes, passes + fails);
|
||
if (fails > 0) System.exit(1);
|
||
}
|
||
}
|