wave8b: 438/196 — rails-0017, hanami-0001, spark-0002 + PDF

This commit is contained in:
russell@unturf.com 2026-03-27 16:33:08 -04:00
parent 2d3e3d603e
commit c4330be5b0
18 changed files with 1069 additions and 8 deletions

View file

@ -0,0 +1,163 @@
package unit;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* janusgraph-0001: MultiCondition extends ArrayList O(C²) condition dedup
*
* Simulates JanusGraph's MultiCondition.contains() / QueryUtil.addConstraint()
* pattern where conditions are stored in an ArrayList but membership is tested
* with contains() before each add().
*
* Slow path: ArrayList-backed condition set (as in MultiCondition).
* Fast path: HashSet-backed condition set (proposed fix).
*
* Each "condition" (PredicateCondition) is a unique Integer-keyed object.
* We count equals() calls to measure O(C²) vs O(C) growth.
*/
public class JanusGraphTest {
static int slowEqOps = 0;
static int fastEqOps = 0;
static class SlowCondition {
final int id;
SlowCondition(int id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof SlowCondition)) return false;
slowEqOps++;
return this.id == ((SlowCondition) o).id;
}
@Override
public int hashCode() {
// Deliberately return constant to simulate broken hash worst-case
// for HashSet (though that's not what we're testing here; this tests
// the ArrayList path which never uses hashCode).
return 42;
}
}
static class FastCondition {
final int id;
FastCondition(int id) { this.id = id; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FastCondition)) return false;
fastEqOps++;
return this.id == ((FastCondition) o).id;
}
@Override
public int hashCode() {
return id; // proper hash O(1) HashSet lookup
}
}
/**
* Defective path: ArrayList-backed MultiCondition.
* addConstraint() pattern: if (!conditions.contains(pc)) conditions.add(pc)
* O(i) scan for the i-th unique item => O(C^2) total.
*/
static void slowPath(int n) {
List<SlowCondition> conditions = new ArrayList<>();
for (int i = 0; i < n; i++) {
SlowCondition pc = new SlowCondition(i);
if (!conditions.contains(pc)) {
conditions.add(pc);
}
}
// Second pass: try adding duplicates (all present full scan each time)
for (int i = 0; i < n; i++) {
SlowCondition pc = new SlowCondition(i);
if (!conditions.contains(pc)) {
conditions.add(pc);
}
}
}
/**
* Fixed path: HashSet-backed condition set with preserved insertion order.
* contains() is O(1) regardless of set size.
*/
static void fastPath(int n) {
Set<FastCondition> conditionSet = new LinkedHashSet<>();
for (int i = 0; i < n; i++) {
FastCondition pc = new FastCondition(i);
conditionSet.add(pc); // Set.add() deduplicates with O(1) contains check
}
// Second pass: duplicates O(1) per add, idempotent
for (int i = 0; i < n; i++) {
conditionSet.add(new FastCondition(i));
}
}
public static void main(String[] args) {
int N = 400;
int PASSES = 3;
// Warm up
slowPath(10);
fastPath(10);
slowEqOps = 0;
fastEqOps = 0;
// Measure
for (int p = 0; p < PASSES; p++) {
slowPath(N);
fastPath(N);
}
// slow: first-pass adds N unique items, i-th add scans i existing => N*(N-1)/2
// second-pass each of N items scans full N => N*N
// per call total: ~1.5*N^2; for PASSES calls: ~1.5*N^2*PASSES
long expectedSlowMin = (long)(N * N / 4); // conservative lower bound
// fast: proper hash, each add/lookup is O(1), ~0 equals() for distinct ids
long expectedFastMax = (long)(N * PASSES * 4); // generous upper bound
System.out.println("N=" + N + " PASSES=" + PASSES);
System.out.println("slow equals ops : " + slowEqOps + " (expected >=" + expectedSlowMin + ")");
System.out.println("fast equals ops : " + fastEqOps + " (expected <=" + expectedFastMax + ")");
int passed = 0;
int total = 0;
total++;
if (slowEqOps >= expectedSlowMin) {
System.out.println("PASS 1/" + total + ": slow path O(n^2) confirmed (ops=" + slowEqOps + " >= " + expectedSlowMin + ")");
passed++;
} else {
System.out.println("FAIL 1/" + total + ": slow ops=" + slowEqOps + " < expected " + expectedSlowMin);
}
total++;
if (fastEqOps <= expectedFastMax) {
System.out.println("PASS 2/" + total + ": fast path O(1) confirmed (ops=" + fastEqOps + " <= " + expectedFastMax + ")");
passed++;
} else {
System.out.println("FAIL 2/" + total + ": fast ops=" + fastEqOps + " > expected " + expectedFastMax);
}
total++;
boolean ratioOk = fastEqOps == 0 || slowEqOps >= fastEqOps * 10;
if (ratioOk) {
System.out.println("PASS 3/" + total + ": speedup ratio slow/fast = " +
(fastEqOps == 0 ? "inf" : (slowEqOps / fastEqOps)) + "x (expected >=10x)");
passed++;
} else {
System.out.println("FAIL 3/" + total + ": ratio too small: slow=" + slowEqOps + " fast=" + fastEqOps);
}
System.out.println(passed + "/" + total + " PASS");
if (passed != total) System.exit(1);
}
}