160 lines
6 KiB
Java
160 lines
6 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* wasmer-0001: Ruleset Vec<Rule> O(n) linear scan on every network operation.
|
|
*
|
|
* Models the Ruleset.allows_socket() hot path:
|
|
* slow: rules stored as flat List, every check scans all rules (O(n))
|
|
* fast: rules partitioned by type at insert time; socket check only scans
|
|
* ip_rules, domain check only scans dns_rules (O(n_ip) / O(n_dns))
|
|
*
|
|
* Benchmark: N rules total = N/2 IP + N/2 DNS.
|
|
* slow: allows_socket scans all N rules per call.
|
|
* fast: allows_socket scans N/2 IP rules per call.
|
|
* With M=N calls: slow=O(N^2), fast=O((N/2)*N) => 2x minimum.
|
|
* At high DNS:IP ratio (e.g. 90% DNS), speedup approaches 10x.
|
|
*/
|
|
public class RulesetLinearScanTest {
|
|
|
|
// Rule types
|
|
enum RuleKind { IP, DNS, NEG_IP, NEG_DNS }
|
|
|
|
static class Rule {
|
|
final RuleKind kind;
|
|
final int id; // synthetic payload to prevent JIT elimination
|
|
Rule(RuleKind kind, int id) { this.kind = kind; this.id = id; }
|
|
|
|
boolean allowsSocket(int addr) {
|
|
// Simulate per-rule check cost: kind-dispatch + comparison
|
|
return kind == RuleKind.IP && id == addr;
|
|
}
|
|
boolean blocksSocket(int addr) {
|
|
return kind == RuleKind.NEG_IP && id == addr;
|
|
}
|
|
boolean allowsDomain(String domain) {
|
|
return kind == RuleKind.DNS && domain.hashCode() == id;
|
|
}
|
|
boolean blocksDomain(String domain) {
|
|
return kind == RuleKind.NEG_DNS && domain.hashCode() == id;
|
|
}
|
|
}
|
|
|
|
/** SLOW: flat Vec<Rule> — scans all N rules per allows_socket call */
|
|
static class SlowRuleset {
|
|
final List<Rule> rules = new ArrayList<>();
|
|
|
|
void addRule(Rule r) { rules.add(r); }
|
|
|
|
boolean allowsSocket(int addr) {
|
|
// Two full scans (blocks check + allows check)
|
|
for (Rule r : rules) { if (r.blocksSocket(addr)) return false; }
|
|
for (Rule r : rules) { if (r.allowsSocket(addr)) return true; }
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** FAST: partitioned — socket check only scans ip_rules */
|
|
static class FastRuleset {
|
|
final List<Rule> ip_rules = new ArrayList<>();
|
|
final List<Rule> dns_rules = new ArrayList<>();
|
|
|
|
void addRule(Rule r) {
|
|
switch (r.kind) {
|
|
case DNS: dns_rules.add(r); break;
|
|
case NEG_DNS: dns_rules.add(r); break;
|
|
default: ip_rules.add(r); break;
|
|
}
|
|
}
|
|
|
|
boolean allowsSocket(int addr) {
|
|
// Only scans ip_rules — DNS rules never visited
|
|
for (Rule r : ip_rules) { if (r.blocksSocket(addr)) return false; }
|
|
for (Rule r : ip_rules) { if (r.allowsSocket(addr)) return true; }
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Benchmark helper.
|
|
* @param ruleset slow or fast instance (via interface)
|
|
* @param addFn lambda to add a rule
|
|
* @param checkFn lambda to perform one allows_socket check
|
|
* @param N number of rules
|
|
* @param M number of check calls
|
|
* @return exact operation count (comparisons performed)
|
|
*/
|
|
static long bench(boolean slow, int N, int M, int dnsRatio) {
|
|
// dnsRatio: percentage of rules that are DNS (0-100)
|
|
// slow path scans all N per call; fast path scans N*(1-dnsRatio/100) per call
|
|
|
|
// Build rulesets
|
|
SlowRuleset slowRs = slow ? new SlowRuleset() : null;
|
|
FastRuleset fastRs = slow ? null : new FastRuleset();
|
|
|
|
int ipCount = N - (N * dnsRatio / 100);
|
|
int dnsCount = N * dnsRatio / 100;
|
|
|
|
for (int i = 0; i < ipCount; i++) {
|
|
Rule r = new Rule(RuleKind.IP, i + 10000);
|
|
if (slow) slowRs.addRule(r); else fastRs.addRule(r);
|
|
}
|
|
for (int i = 0; i < dnsCount; i++) {
|
|
Rule r = new Rule(RuleKind.DNS, i + 20000);
|
|
if (slow) slowRs.addRule(r); else fastRs.addRule(r);
|
|
}
|
|
|
|
// Run M checks — addr never matches any rule (worst case full scan)
|
|
int addr = 99999;
|
|
long ops = 0;
|
|
for (int i = 0; i < M; i++) {
|
|
if (slow) {
|
|
ops += slowRs.rules.size(); // blocks scan
|
|
slowRs.allowsSocket(addr);
|
|
ops += slowRs.rules.size(); // allows scan
|
|
} else {
|
|
ops += fastRs.ip_rules.size(); // blocks scan (ip only)
|
|
fastRs.allowsSocket(addr);
|
|
ops += fastRs.ip_rules.size(); // allows scan (ip only)
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
|
|
static void test(String name, int N, int M, int dnsRatio, int minSpeedup) {
|
|
long sOps = bench(true, N, M, dnsRatio);
|
|
long fOps = bench(false, N, M, dnsRatio);
|
|
|
|
double speedup = (double) sOps / fOps;
|
|
boolean pass = sOps >= fOps * minSpeedup;
|
|
|
|
System.out.printf("%-40s slow=%,d fast=%,d speedup=%.1fx %s%n",
|
|
name, sOps, fOps, speedup, pass ? "PASS" : "FAIL");
|
|
|
|
assert pass : String.format(
|
|
"%s: expected speedup >=%dx, got %.1fx (slow=%d, fast=%d)",
|
|
name, minSpeedup, speedup, sOps, fOps);
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("wasmer-0001: Ruleset linear scan");
|
|
System.out.println("=================================");
|
|
|
|
// 50% DNS rules: fast path scans half as many rules => 2x minimum
|
|
test("N=100 M=100 dns=50% minSpeedup=2x", 100, 100, 50, 2);
|
|
test("N=500 M=500 dns=50% minSpeedup=2x", 500, 500, 50, 2);
|
|
|
|
// 80% DNS rules: fast path scans 20% of rules => 5x minimum
|
|
test("N=100 M=100 dns=80% minSpeedup=5x", 100, 100, 80, 5);
|
|
test("N=500 M=500 dns=80% minSpeedup=5x", 500, 500, 80, 5);
|
|
|
|
// 90% DNS rules: fast path scans 10% of rules => 10x minimum (HIGH severity threshold)
|
|
test("N=200 M=200 dns=90% minSpeedup=10x", 200, 200, 90, 10);
|
|
test("N=1000 M=100 dns=90% minSpeedup=10x", 1000, 100, 90, 10);
|
|
|
|
System.out.println("=================================");
|
|
System.out.println("ALL PASS");
|
|
}
|
|
}
|