whitepaper: re-add 10 missing entries + 11 new defects this session, count 578→590; rebuild PDF
This commit is contained in:
parent
e4ee168b1e
commit
2e4f7807d5
401 changed files with 3914 additions and 114 deletions
208
defects/javac/unit/NotifyChangeAlgorithm.java
Normal file
208
defects/javac/unit/NotifyChangeAlgorithm.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue