247 lines
8.7 KiB
Java
247 lines
8.7 KiB
Java
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);
|
||
}
|
||
}
|
||
}
|