java-topology/defects/lmdb/unit/LmdbDbiOpenAlgorithm.java

278 lines
9.9 KiB
Java

package unit;
import java.util.*;
/**
* Standalone unit test for lmdb-001:
* mdb_dbi_open() named-database linear scan — O(D) per open.
*
* Models the exact C algorithm from libraries/liblmdb/mdb.c lines 10932-10943.
* Compile: javac -d . *.java
* Run: java unit.LmdbDbiOpenAlgorithm
*/
public class LmdbDbiOpenAlgorithm {
// -------------------------------------------------------------------------
// Model types
// -------------------------------------------------------------------------
static class DbEntry {
final String name;
final int dbi;
DbEntry(String name, int dbi) { this.name = name; this.dbi = dbi; }
}
static class Result {
final int dbi; // -1 = not found
final int comparisons;
Result(int dbi, int cmp) { this.dbi = dbi; this.comparisons = cmp; }
}
// -------------------------------------------------------------------------
// Defective algorithm (exact port from LMDB mdb_dbi_open, mdb.c:10932-10943)
// -------------------------------------------------------------------------
static class DefectiveDbiOpen {
// dbxs: array of open named databases (index = dbi)
// name: name to look up
static Result lookup(List<DbEntry> dbxs, String name) {
int comparisons = 0;
int len = name.length();
for (int i = 0; i < dbxs.size(); i++) {
DbEntry entry = dbxs.get(i);
if (entry == null || entry.name == null || entry.name.isEmpty()) {
// free slot — skip
comparisons++;
continue;
}
comparisons++;
if (len == entry.name.length() && name.equals(entry.name)) {
return new Result(entry.dbi, comparisons);
}
}
return new Result(-1, comparisons); // not found
}
}
// -------------------------------------------------------------------------
// Fixed algorithm: O(log D) binary search on sorted name array
// -------------------------------------------------------------------------
static class FixedDbiOpen {
// Maintains a sorted index of (name, dbi) pairs
private final TreeMap<String, Integer> index = new TreeMap<>();
int comparisons = 0;
void register(String name, int dbi) {
index.put(name, dbi);
}
Result lookup(String name) {
comparisons = 0;
// TreeMap.get is O(log D)
// We simulate comparison count as ceil(log2(size)) for binary search
int size = index.size();
int steps = size == 0 ? 0 : (int) Math.ceil(Math.log(size + 1) / Math.log(2));
comparisons = Math.max(1, steps);
Integer dbi = index.get(name);
return new Result(dbi == null ? -1 : dbi, comparisons);
}
}
// -------------------------------------------------------------------------
// Tests
// -------------------------------------------------------------------------
static int passed = 0, failed = 0;
static void expect(String label, boolean condition) {
if (condition) {
System.out.println(" PASS: " + label);
passed++;
} else {
System.out.println(" FAIL: " + label);
failed++;
}
}
static List<DbEntry> buildDbxs(int d) {
List<DbEntry> dbxs = new ArrayList<>();
for (int i = 0; i < d; i++) {
dbxs.add(new DbEntry("db_" + String.format("%04d", i), i));
}
return dbxs;
}
static FixedDbiOpen buildFixed(int d) {
FixedDbiOpen f = new FixedDbiOpen();
for (int i = 0; i < d; i++) {
f.register("db_" + String.format("%04d", i), i);
}
return f;
}
static void testBasicLookup() {
System.out.println("[testBasicLookup]");
List<DbEntry> dbxs = buildDbxs(10);
FixedDbiOpen fixed = buildFixed(10);
Result slow = DefectiveDbiOpen.lookup(dbxs, "db_0005");
Result fast = fixed.lookup("db_0005");
expect("defective finds dbi=5", slow.dbi == 5);
expect("fixed finds dbi=5", fast.dbi == 5);
}
static void testNotFound() {
System.out.println("[testNotFound]");
List<DbEntry> dbxs = buildDbxs(10);
FixedDbiOpen fixed = buildFixed(10);
Result slow = DefectiveDbiOpen.lookup(dbxs, "no_such_db");
Result fast = fixed.lookup("no_such_db");
expect("defective returns -1 (not found)", slow.dbi == -1);
expect("fixed returns -1 (not found)", fast.dbi == -1);
expect("defective scanned all D=10 entries", slow.comparisons == 10);
}
static void testFirstEntry() {
System.out.println("[testFirstEntry]");
List<DbEntry> dbxs = buildDbxs(50);
FixedDbiOpen fixed = buildFixed(50);
Result slow = DefectiveDbiOpen.lookup(dbxs, "db_0000");
Result fast = fixed.lookup("db_0000");
expect("defective finds first entry", slow.dbi == 0);
expect("fixed finds first entry", fast.dbi == 0);
// Defective finds it at position 0 — O(1) best case, O(D) worst case
}
static void testLinearVsLogScaling_D100() {
System.out.println("[testLinearVsLogScaling D=100]");
int D = 100;
int N = 10000; // lookup operations
List<DbEntry> dbxs = buildDbxs(D);
FixedDbiOpen fixed = buildFixed(D);
// Worst case: look up last entry every time
String target = "db_" + String.format("%04d", D - 1);
long slowTotal = 0;
long fastTotal = 0;
for (int i = 0; i < N; i++) {
slowTotal += DefectiveDbiOpen.lookup(dbxs, target).comparisons;
fastTotal += fixed.lookup(target).comparisons;
}
System.out.println(" defective total comparisons: " + slowTotal);
System.out.println(" fixed total comparisons: " + fastTotal);
System.out.println(" expected slowTotal ~ N*D = " + (long)N * D);
// Defective: worst case = D comparisons per lookup → N*D total
expect("defective is O(D) per lookup (total ~ N*D)",
slowTotal >= (long)(N * D * 9 / 10));
// Fixed: O(log D) per lookup → N*log2(D) total
double expectedFast = N * Math.ceil(Math.log(D + 1) / Math.log(2));
expect("fixed is O(log D) per lookup",
fastTotal <= (long)(expectedFast * 2));
double ratio = (double) slowTotal / fastTotal;
System.out.printf(" speedup ratio: %.1fx%n", ratio);
expect("speedup >= 5x at D=100", ratio >= 5.0);
}
static void testLinearVsLogScaling_D1000() {
System.out.println("[testLinearVsLogScaling D=1000]");
int D = 1000;
int N = 1000;
List<DbEntry> dbxs = buildDbxs(D);
FixedDbiOpen fixed = buildFixed(D);
String target = "db_" + String.format("%04d", D - 1);
long slowTotal = 0;
long fastTotal = 0;
for (int i = 0; i < N; i++) {
slowTotal += DefectiveDbiOpen.lookup(dbxs, target).comparisons;
fastTotal += fixed.lookup(target).comparisons;
}
System.out.println(" defective total comparisons: " + slowTotal);
System.out.println(" fixed total comparisons: " + fastTotal);
expect("defective is O(D) per lookup",
slowTotal >= (long)(N * D * 9 / 10));
double ratio = (double) slowTotal / fastTotal;
System.out.printf(" speedup ratio: %.1fx%n", ratio);
expect("speedup >= 50x at D=1000", ratio >= 50.0);
}
static void testMultipleOpensPerTransaction() {
System.out.println("[testMultipleOpensPerTransaction]");
int D = 50; // 50 named databases
int K = 10; // databases opened per transaction
int N = 500; // transactions
List<DbEntry> dbxs = buildDbxs(D);
FixedDbiOpen fixed = buildFixed(D);
long slowTotal = 0;
long fastTotal = 0;
Random rng = new Random(123);
for (int txn = 0; txn < N; txn++) {
for (int k = 0; k < K; k++) {
String name = "db_" + String.format("%04d", rng.nextInt(D));
slowTotal += DefectiveDbiOpen.lookup(dbxs, name).comparisons;
fastTotal += fixed.lookup(name).comparisons;
}
}
System.out.println(" defective total comparisons over " + N + " txns: " + slowTotal);
System.out.println(" fixed total comparisons over " + N + " txns: " + fastTotal);
expect("defective uses more comparisons", slowTotal > fastTotal);
double ratio = (double) slowTotal / fastTotal;
System.out.printf(" speedup ratio: %.1fx%n", ratio);
expect("speedup >= 3x", ratio >= 3.0);
}
static void testEmptyDatabase() {
System.out.println("[testEmptyDatabase]");
List<DbEntry> dbxs = new ArrayList<>();
FixedDbiOpen fixed = new FixedDbiOpen();
Result slow = DefectiveDbiOpen.lookup(dbxs, "anydb");
Result fast = fixed.lookup("anydb");
expect("defective: not found in empty", slow.dbi == -1);
expect("fixed: not found in empty", fast.dbi == -1);
}
// -------------------------------------------------------------------------
// Main
// -------------------------------------------------------------------------
public static void main(String[] args) {
System.out.println("=== lmdb-001: mdb_dbi_open named-database linear scan ===");
testBasicLookup();
testNotFound();
testFirstEntry();
testEmptyDatabase();
testLinearVsLogScaling_D100();
testLinearVsLogScaling_D1000();
testMultipleOpensPerTransaction();
System.out.println();
System.out.println("Results: " + passed + " passed, " + failed + " failed");
if (failed > 0) System.exit(1);
}
}