whitepaper: re-add 10 missing entries + 11 new defects this session, count 578→590; rebuild PDF

This commit is contained in:
russell@unturf.com 2026-03-27 22:18:31 -04:00
parent e4ee168b1e
commit 2e4f7807d5
401 changed files with 3914 additions and 114 deletions

View file

@ -0,0 +1,208 @@
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);
}
}