278 lines
9.9 KiB
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);
|
|
}
|
|
}
|