186 lines
6.8 KiB
Java
186 lines
6.8 KiB
Java
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<String> indexedVars, List<String> 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<String> indexedVars, List<String> keyNames) {
|
||
// Build map: O(K)
|
||
HashMap<String, Integer> 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<String> 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<String> 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<String> keyNames = new ArrayList<>(numKeys);
|
||
for (int n = 0; n < numKeys; n++) {
|
||
keyNames.add("key_" + n);
|
||
}
|
||
List<String> 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);
|
||
}
|
||
}
|