wave8b: 438/196 — rails-0017, hanami-0001, spark-0002 + PDF
This commit is contained in:
parent
2d3e3d603e
commit
c4330be5b0
18 changed files with 1069 additions and 8 deletions
172
defects/neo4j/unit/Neo4jTest.java
Normal file
172
defects/neo4j/unit/Neo4jTest.java
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
package unit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* neo4j-0001: Dijkstra predecessors List.contains() — O(E×P) vs O(E)
|
||||
*
|
||||
* Simulates the predecessor deduplication guard from Dijkstra.java:324.
|
||||
* The defective path uses List<String> (ArrayList.contains = O(N)).
|
||||
* The fixed path uses Set<String> (HashSet.contains = O(1)).
|
||||
*
|
||||
* Each "relationship" is a unique String object.
|
||||
* We measure how many equality comparisons .contains() triggers
|
||||
* by wrapping items in a counted comparator object.
|
||||
*/
|
||||
public class Neo4jTest {
|
||||
|
||||
static int slowContainsOps = 0;
|
||||
static int fastContainsOps = 0;
|
||||
|
||||
static class CountedRel {
|
||||
final int id;
|
||||
CountedRel(int id) { this.id = id; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof CountedRel)) return false;
|
||||
// Count each comparison
|
||||
slowContainsOps++;
|
||||
return this.id == ((CountedRel) o).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
static class CountedRelFast {
|
||||
final int id;
|
||||
CountedRelFast(int id) { this.id = id; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof CountedRelFast)) return false;
|
||||
fastContainsOps++;
|
||||
return this.id == ((CountedRelFast) o).id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id; // proper hash — Set.contains() can short-circuit
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defective path: List<Rel> predecessors.
|
||||
* For N predecessors, each contains() call costs O(N) in the worst case
|
||||
* (item not present triggers full scan).
|
||||
*/
|
||||
static void slowPath(int n) {
|
||||
Map<Integer, List<CountedRel>> predecessors = new HashMap<>();
|
||||
// Simulate adding n unique relationships to the predecessor list for node 0
|
||||
predecessors.put(0, new ArrayList<>());
|
||||
for (int i = 0; i < n; i++) {
|
||||
CountedRel rel = new CountedRel(i);
|
||||
List<CountedRel> preds = predecessors.get(0);
|
||||
// This is the defective guard: O(P) scan per call
|
||||
if (!preds.contains(rel)) {
|
||||
preds.add(rel);
|
||||
}
|
||||
}
|
||||
// Now simulate a second pass: trying to add the same rels again (duplicates)
|
||||
// Each contains() now scans all n existing entries
|
||||
for (int i = 0; i < n; i++) {
|
||||
CountedRel rel = new CountedRel(i);
|
||||
List<CountedRel> preds = predecessors.get(0);
|
||||
if (!preds.contains(rel)) {
|
||||
preds.add(rel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fixed path: Set<Rel> predecessors.
|
||||
* HashSet.contains() is O(1) — hash lookup, equals only called on collision.
|
||||
*/
|
||||
static void fastPath(int n) {
|
||||
Map<Integer, Set<CountedRelFast>> predecessors = new HashMap<>();
|
||||
predecessors.put(0, new LinkedHashSet<>());
|
||||
for (int i = 0; i < n; i++) {
|
||||
CountedRelFast rel = new CountedRelFast(i);
|
||||
Set<CountedRelFast> preds = predecessors.get(0);
|
||||
preds.add(rel); // Set.add() deduplicates; no explicit contains() needed
|
||||
}
|
||||
// Duplicate pass: set semantics are idempotent
|
||||
for (int i = 0; i < n; i++) {
|
||||
CountedRelFast rel = new CountedRelFast(i);
|
||||
predecessors.get(0).add(rel);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
int N = 500;
|
||||
int PASSES = 3;
|
||||
|
||||
// Warm up
|
||||
slowPath(10);
|
||||
fastPath(10);
|
||||
slowContainsOps = 0;
|
||||
fastContainsOps = 0;
|
||||
|
||||
// Measure
|
||||
for (int p = 0; p < PASSES; p++) {
|
||||
slowPath(N);
|
||||
fastPath(N);
|
||||
}
|
||||
|
||||
// For the slow path: first pass adds N items with 0..N-1 scans = N*(N-1)/2 ops
|
||||
// Second pass finds all N items present: each scan goes full N = N*N ops
|
||||
// Total per call: ~N^2/2 + N^2 = ~1.5*N^2 comparisons
|
||||
// For fast path: hash collisions are rare; ideally ~0 equals() calls for unique ids
|
||||
|
||||
long expectedSlowMin = (long)(N * N / 4); // conservative lower bound per pass
|
||||
long expectedFastMax = (long)(N * PASSES * 2); // generous upper bound
|
||||
|
||||
System.out.println("N=" + N + " PASSES=" + PASSES);
|
||||
System.out.println("slow contains ops : " + slowContainsOps + " (expected >=" + expectedSlowMin + ")");
|
||||
System.out.println("fast contains ops : " + fastContainsOps + " (expected <=" + expectedFastMax + ")");
|
||||
|
||||
int passed = 0;
|
||||
int total = 0;
|
||||
|
||||
total++;
|
||||
if (slowContainsOps >= expectedSlowMin) {
|
||||
System.out.println("PASS 1/" + total + ": slow path O(n^2) confirmed (ops=" + slowContainsOps + " >= " + expectedSlowMin + ")");
|
||||
passed++;
|
||||
} else {
|
||||
System.out.println("FAIL 1/" + total + ": slow path did not show expected O(n^2) ops");
|
||||
}
|
||||
|
||||
total++;
|
||||
if (fastContainsOps <= expectedFastMax) {
|
||||
System.out.println("PASS 2/" + total + ": fast path O(1) confirmed (ops=" + fastContainsOps + " <= " + expectedFastMax + ")");
|
||||
passed++;
|
||||
} else {
|
||||
System.out.println("FAIL 2/" + total + ": fast path showed too many comparisons: " + fastContainsOps);
|
||||
}
|
||||
|
||||
total++;
|
||||
// Ratio must be at least 10x
|
||||
boolean ratioOk = slowContainsOps >= fastContainsOps * 10;
|
||||
if (fastContainsOps == 0 || ratioOk) {
|
||||
System.out.println("PASS 3/" + total + ": ratio slow/fast is " +
|
||||
(fastContainsOps == 0 ? "inf" : (slowContainsOps / fastContainsOps)) + "x (expected >=10x)");
|
||||
passed++;
|
||||
} else {
|
||||
System.out.println("FAIL 3/" + total + ": ratio too small: slow=" + slowContainsOps + " fast=" + fastContainsOps);
|
||||
}
|
||||
|
||||
System.out.println(passed + "/" + total + " PASS");
|
||||
if (passed != total) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue