package unit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * nginx-0003: ngx_http_variables_init_vars O(V×K) nested scan at startup. * * Models the resolution of V indexed variables against K registered variable * keys using ngx_strncmp (SLOW: nested loop) vs a HashMap (FAST: O(1) lookup). * * Run: javac NginxVariablesInitAlgorithmTest.java && java unit.NginxVariablesInitAlgorithmTest */ public class NginxVariablesInitAlgorithmTest { static long slowOps = 0; static long fastOps = 0; /** * SLOW: O(V×K) nested loop — mirrors ngx_http_variables_init_vars. * For each indexed variable, scan all variable_keys entries with strcmp. * * @param indexedVars list of indexed variable names (V) * @param keyNames list of registered key names (K) * @param handlers map from key name to handler index (simulated) * @return resolved handler array (one per indexed var, -1 if not found) */ static int[] resolveVarsSlow(List indexedVars, List keyNames) { int[] handlers = new int[indexedVars.size()]; for (int i = 0; i < indexedVars.size(); i++) { // outer: V handlers[i] = -1; for (int n = 0; n < keyNames.size(); n++) { // inner: K slowOps++; if (indexedVars.get(i).equals(keyNames.get(n))) { handlers[i] = n; // "set handler" break; } } } return handlers; } /** * FAST: O(V + K) — build a HashMap from keyNames first, then O(1) per var. * Models the fix: build a temporary hash of variables_keys, then resolve. */ static int[] resolveVarsFast(List indexedVars, List keyNames) { // Build map: O(K) HashMap keyMap = new HashMap<>(keyNames.size() * 2); for (int n = 0; n < keyNames.size(); n++) { fastOps++; keyMap.put(keyNames.get(n), n); } // Resolve: O(V) int[] handlers = new int[indexedVars.size()]; for (int i = 0; i < indexedVars.size(); i++) { fastOps++; Integer h = keyMap.get(indexedVars.get(i)); handlers[i] = (h != null) ? h : -1; } return handlers; } static boolean runTest(String name, int numVars, int numKeys, int expectedResolved) { // Build keyNames: all registered variable names (built-ins + module vars) List keyNames = new ArrayList<>(numKeys); for (int n = 0; n < numKeys; n++) { keyNames.add("var_key_" + n); } // Build indexedVars: a subset of keyNames (vars referenced in config) // First numVars entries from keyNames are referenced List indexedVars = new ArrayList<>(numVars); for (int i = 0; i < numVars; i++) { indexedVars.add("var_key_" + i); // all should resolve } slowOps = 0; fastOps = 0; int[] slowResult = resolveVarsSlow(indexedVars, keyNames); long slowCount = slowOps; slowOps = 0; fastOps = 0; int[] fastResult = resolveVarsFast(indexedVars, keyNames); long fastCount = fastOps; // Verify correctness int resolvedSlow = 0, resolvedFast = 0; for (int i = 0; i < numVars; i++) { if (slowResult[i] != -1) resolvedSlow++; if (fastResult[i] != -1) resolvedFast++; if (slowResult[i] != fastResult[i]) { System.out.printf("FAIL [%s] N=%d,K=%d: result mismatch at i=%d slow=%d fast=%d%n", name, numVars, numKeys, i, slowResult[i], fastResult[i]); return false; } } if (resolvedSlow != expectedResolved) { System.out.printf("FAIL [%s] N=%d,K=%d: expected %d resolved, got %d%n", name, numVars, numKeys, expectedResolved, resolvedSlow); return false; } double ratio = (double) slowCount / fastCount; System.out.printf("PASS [%s] N=%d K=%d resolved=%d slow=%d fast=%d ratio=%.1fx%n", name, numVars, numKeys, resolvedSlow, slowCount, fastCount, ratio); if (numVars >= 50 && ratio < 5.0) { System.out.printf("FAIL [%s] ratio %.1f < 5.0 minimum%n", name, ratio); return false; } return true; } public static void main(String[] args) { int passed = 0, total = 0; // Test 1: small config — V=10, K=60 (built-ins only) total++; if (runTest("small-config", 10, 60, 10)) passed++; // Test 2: medium config — V=50, K=150 (built-ins + module vars) total++; if (runTest("medium-config", 50, 150, 50)) passed++; // Test 3: large config — V=150, K=300 (heavy module load) total++; if (runTest("large-config", 150, 300, 150)) passed++; // Test 4: extra-large — V=300, K=500 total++; if (runTest("xlarge-config", 300, 500, 300)) passed++; // Test 5: partial resolution (some vars are prefix-matched, not in key list) // Only first half of indexed vars are in keyNames { total++; int numVars = 100; int numKeys = 200; List keyNames = new ArrayList<>(numKeys); for (int n = 0; n < numKeys; n++) { keyNames.add("key_" + n); } List indexedVars = new ArrayList<>(numVars); for (int i = 0; i < numVars; i++) { if (i < numVars / 2) { indexedVars.add("key_" + i); // will resolve } else { indexedVars.add("prefix_var_" + i); // will NOT resolve via keys (prefix path) } } slowOps = 0; int[] slowResult = resolveVarsSlow(indexedVars, keyNames); long slowCount = slowOps; slowOps = 0; int[] fastResult = resolveVarsFast(indexedVars, keyNames); long fastCount = fastOps; boolean ok = true; for (int i = 0; i < numVars; i++) { if (slowResult[i] != fastResult[i]) { ok = false; break; } } double ratio = (double) slowCount / fastCount; if (ok) { System.out.printf("PASS [partial-resolution] N=%d K=%d slow=%d fast=%d ratio=%.1fx%n", numVars, numKeys, slowCount, fastCount, ratio); passed++; } else { System.out.printf("FAIL [partial-resolution] result mismatch%n"); } } System.out.printf("%n%d/%d PASS%n", passed, total); if (passed != total) System.exit(1); } }