java-topology/defects/tcl/unit/TclDoImportAlgorithm.java

247 lines
8.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* tcl-0001: DoImport O(C×P) export pattern scan per namespace import
*
* Models Tcl's namespace import machinery.
* SLOW: for each of C commands, linear scan P export patterns (current Tcl code)
* FAST: pre-build a HashSet of exported command names; O(1) per command check
*
* In Tcl_Import with wildcard pattern, DoImport is called for each of C commands
* that match the import pattern. Each DoImport call scans P export patterns
* using Tcl_StringMatch. Total: O(C × P).
*/
public class TclDoImportAlgorithm {
// Simple wildcard matcher (subset of Tcl_StringMatch)
static boolean stringMatch(String str, String pattern) {
if (pattern.equals("*")) return true;
if (pattern.endsWith("*")) {
String prefix = pattern.substring(0, pattern.length() - 1);
return str.startsWith(prefix);
}
return str.equals(pattern);
}
// --- SLOW: linear scan of export patterns per command (current DoImport) ---
static class SlowNamespace {
Map<String, Object> cmdTable = new HashMap<>();
List<String> exportPatterns = new ArrayList<>();
long opCount = 0;
void defineCommand(String name) {
cmdTable.put(name, new Object());
}
void addExportPattern(String pattern) {
exportPatterns.add(pattern);
}
/** Check if cmdName is exported: linear scan over P patterns */
boolean isExported(String cmdName) {
for (String pattern : exportPatterns) {
opCount++;
if (stringMatch(cmdName, pattern)) {
return true;
}
}
return false;
}
/**
* Simulate Tcl_Import with wildcard (imports all commands matching importPat
* that are also exported).
* Outer: O(C) hash iteration; inner: O(P) export check per command = O(C*P).
*/
int importAll(String importPat) {
int count = 0;
for (String cmdName : cmdTable.keySet()) {
if (stringMatch(cmdName, importPat)) {
if (isExported(cmdName)) { // O(P) per command
count++;
}
}
}
return count;
}
}
// --- FAST: HashSet cache of exported command names ---
static class FastNamespace {
Map<String, Object> cmdTable = new HashMap<>();
List<String> exportPatterns = new ArrayList<>();
Set<String> exportedCmdsCache = null; // null = needs rebuild
long opCount = 0;
void defineCommand(String name) {
cmdTable.put(name, new Object());
exportedCmdsCache = null; // invalidate on new command
}
void addExportPattern(String pattern) {
exportPatterns.add(pattern);
exportedCmdsCache = null; // invalidate on export change
}
/** Build the exported-commands cache once: O(C * P) total, amortized O(1) per lookup */
void buildExportCache() {
exportedCmdsCache = new HashSet<>();
for (String cmdName : cmdTable.keySet()) {
for (String pattern : exportPatterns) {
opCount++;
if (stringMatch(cmdName, pattern)) {
exportedCmdsCache.add(cmdName);
break;
}
}
}
}
/** Check if cmdName is exported: O(1) hash lookup */
boolean isExported(String cmdName) {
if (exportedCmdsCache == null) {
buildExportCache();
}
opCount++;
return exportedCmdsCache.contains(cmdName);
}
/**
* Simulate Tcl_Import: O(C) outer, O(1) inner after cache build.
* Cache is built once; subsequent imports are O(C).
*/
int importAll(String importPat) {
int count = 0;
for (String cmdName : cmdTable.keySet()) {
if (stringMatch(cmdName, importPat)) {
if (isExported(cmdName)) { // O(1) after cache
count++;
}
}
}
return count;
}
}
// --- Test harness ---
static int passed = 0;
static int total = 0;
static void check(String name, boolean cond) {
total++;
if (cond) {
passed++;
System.out.println(" PASS: " + name);
} else {
System.out.println(" FAIL: " + name);
}
}
public static void main(String[] args) {
System.out.println("=== tcl-0001: DoImport O(C*P) export pattern scan ===");
int C = 1000; // commands in source namespace (e.g., Tk, Itcl)
int P = 50; // export patterns
SlowNamespace slow = new SlowNamespace();
FastNamespace fast = new FastNamespace();
// Define C commands: half with "pub_" prefix (to be exported), half with "priv_"
for (int i = 0; i < C / 2; i++) {
slow.defineCommand("pub_cmd_" + i);
fast.defineCommand("pub_cmd_" + i);
}
for (int i = 0; i < C / 2; i++) {
slow.defineCommand("priv_cmd_" + i);
fast.defineCommand("priv_cmd_" + i);
}
// P export patterns: most export pub_cmd_* subsets
// First pattern matches everything with prefix "pub_cmd_"
slow.addExportPattern("pub_cmd_*");
fast.addExportPattern("pub_cmd_*");
// Add P-1 more specific patterns (won't match much, but still scanned)
for (int i = 1; i < P; i++) {
slow.addExportPattern("special_" + i + "_*");
fast.addExportPattern("special_" + i + "_*");
}
// First import: correctness check
int slowCount = slow.importAll("*");
int fastCount = fast.importAll("*");
check("slow imports correct count (" + (C/2) + ")", slowCount == C / 2);
check("fast imports correct count (" + (C/2) + ")", fastCount == C / 2);
check("slow and fast agree", slowCount == fastCount);
// Correctness: specific commands
check("slow: pub_cmd_0 is exported", slow.isExported("pub_cmd_0"));
check("fast: pub_cmd_0 is exported", fast.isExported("pub_cmd_0"));
check("slow: priv_cmd_0 is NOT exported", !slow.isExported("priv_cmd_0"));
check("fast: priv_cmd_0 is NOT exported", !fast.isExported("priv_cmd_0"));
// Op count comparison: measure 10 repeated imports (cache warm for fast)
slow.opCount = 0;
fast.opCount = 0;
fast.exportedCmdsCache = null; // start fresh
int importRuns = 10;
for (int r = 0; r < importRuns; r++) {
slow.importAll("*");
fast.importAll("*");
}
long slowOps = slow.opCount;
long fastOps = fast.opCount;
double ratio = (double) slowOps / fastOps;
System.out.println();
System.out.printf("C=%d commands, P=%d export patterns, %d import calls:%n", C, P, importRuns);
System.out.printf(" SLOW ops (linear pattern scan per cmd): %,d%n", slowOps);
System.out.printf(" FAST ops (cache build once + O(1) lookups): %,d%n", fastOps);
System.out.printf(" Ratio: %.1fx%n", ratio);
// SLOW: O(C*P) per call × 10 calls (early exit on first match for half, full scan for other half)
check("slow ops >= C*importRuns", slowOps >= (long) C * importRuns);
// FAST: O(C*P) once for cache build, then O(C) per subsequent import
// Total fast ≈ C*P + (importRuns-1)*C
check("fast ops <= (C*P + importRuns*C*2)", fastOps <= (long)(C * P + importRuns * C * 2));
check("ratio >= 5x", ratio >= 5.0);
// Additional: multiple imports reuse cache (fast stays O(C), slow stays O(C*P))
slow.opCount = 0;
fast.opCount = 0;
int imports = 10;
for (int i = 0; i < imports; i++) {
slow.importAll("*");
fast.importAll("*");
}
long slowOps2 = slow.opCount;
long fastOps2 = fast.opCount;
double ratio2 = (double) slowOps2 / fastOps2;
System.out.printf("%nAfter %d repeated imports (cache hot for fast):%n", imports);
System.out.printf(" SLOW ops: %,d%n", slowOps2);
System.out.printf(" FAST ops: %,d%n", fastOps2);
System.out.printf(" Ratio: %.1fx%n", ratio2);
check("repeated import ratio >= 10x", ratio2 >= 10.0);
System.out.println();
System.out.println(passed + "/" + total + " PASS");
if (passed != total) {
System.exit(1);
}
}
}