149 lines
5.6 KiB
Java
149 lines
5.6 KiB
Java
package unit;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* wasmer-0002: WasiThread signal Vec<Signal> O(n) dedup on every signal delivery.
|
|
*
|
|
* Models the thread.signal() hot path:
|
|
* slow: signals stored as Vec<Signal>, contains() used for dedup => O(n) per insert
|
|
* fast: signals stored as long bitmask => O(1) insert with automatic dedup
|
|
*
|
|
* Also models has_signal(query: &[Signal]):
|
|
* slow: O(S * Q) nested loop (S=pending signals, Q=query len)
|
|
* fast: O(Q) build query mask + O(1) bitmask AND
|
|
*
|
|
* The signal domain is bounded to POSIX signals 1..31 (~30 distinct values).
|
|
* Benchmark: S signals pending, Q queries, N deliver calls.
|
|
* slow: O(S) per deliver (contains check) + O(S*Q) per has_signal call
|
|
* fast: O(1) per deliver + O(Q) per has_signal call
|
|
*/
|
|
public class SignalVecDedupTest {
|
|
|
|
static final int MAX_SIGNAL = 31; // POSIX signals 1..31
|
|
|
|
/** SLOW: Vec<Signal> with linear contains for dedup */
|
|
static class SlowSignalSet {
|
|
final List<Integer> signals = new ArrayList<>();
|
|
|
|
/** Returns ops performed */
|
|
long signal(int sig) {
|
|
long ops = 0;
|
|
for (int s : signals) { ops++; if (s == sig) return ops; } // contains check
|
|
signals.add(sig);
|
|
return ops + 1; // add op
|
|
}
|
|
|
|
/** Returns ops performed */
|
|
long hasSignal(int[] query) {
|
|
long ops = 0;
|
|
for (int s : signals) {
|
|
for (int q : query) {
|
|
ops++;
|
|
if (s == q) return ops;
|
|
}
|
|
}
|
|
return ops;
|
|
}
|
|
}
|
|
|
|
/** FAST: long bitmask — O(1) insert and O(Q) query */
|
|
static class FastSignalSet {
|
|
long mask = 0L;
|
|
|
|
/** Returns ops performed (always 1: single bit-set) */
|
|
long signal(int sig) {
|
|
mask |= (1L << sig);
|
|
return 1;
|
|
}
|
|
|
|
/** Returns ops performed: Q bit-set ops + 1 AND */
|
|
long hasSignal(int[] query) {
|
|
long qmask = 0L;
|
|
for (int q : query) qmask |= (1L << q);
|
|
return query.length + 1; // Q ops to build mask + 1 AND
|
|
}
|
|
}
|
|
|
|
static long benchSignal(boolean slow, int N, int S) {
|
|
// Deliver N signals cycling through S distinct signal values
|
|
SlowSignalSet slowSet = slow ? new SlowSignalSet() : null;
|
|
FastSignalSet fastSet = slow ? null : new FastSignalSet();
|
|
|
|
long totalOps = 0;
|
|
for (int i = 0; i < N; i++) {
|
|
int sig = (i % S) + 1; // signals 1..S
|
|
if (slow) totalOps += slowSet.signal(sig);
|
|
else totalOps += fastSet.signal(sig);
|
|
}
|
|
return totalOps;
|
|
}
|
|
|
|
static long benchHasSignal(boolean slow, int S, int Q, int calls) {
|
|
// S signals pending, query Q signals, repeat 'calls' times
|
|
SlowSignalSet slowSet = slow ? new SlowSignalSet() : null;
|
|
FastSignalSet fastSet = slow ? null : new FastSignalSet();
|
|
|
|
// Pre-fill with S pending signals (signals 1..S)
|
|
for (int s = 1; s <= S; s++) {
|
|
if (slow) slowSet.signal(s); else fastSet.signal(s);
|
|
}
|
|
// Query for signals that are NOT pending (worst-case: full scan required)
|
|
// Use signals S+1..S+Q (not in the pending set)
|
|
int[] queryArr = new int[Q];
|
|
for (int q = 0; q < Q; q++) queryArr[q] = S + q + 1;
|
|
|
|
long totalOps = 0;
|
|
for (int c = 0; c < calls; c++) {
|
|
if (slow) totalOps += slowSet.hasSignal(queryArr);
|
|
else totalOps += fastSet.hasSignal(queryArr);
|
|
}
|
|
return totalOps;
|
|
}
|
|
|
|
static void testSignal(String name, int N, int S, int minSpeedup) {
|
|
long sOps = benchSignal(true, N, S);
|
|
long fOps = benchSignal(false, N, S);
|
|
|
|
double speedup = (double) sOps / fOps;
|
|
boolean pass = sOps >= fOps * minSpeedup;
|
|
|
|
System.out.printf(" signal %-42s slow=%,d fast=%,d speedup=%.1fx %s%n",
|
|
name, sOps, fOps, speedup, pass ? "PASS" : "FAIL");
|
|
assert pass : String.format(
|
|
"signal %s: expected speedup >=%dx, got %.1fx", name, minSpeedup, speedup);
|
|
}
|
|
|
|
static void testHasSignal(String name, int S, int Q, int calls, int minSpeedup) {
|
|
long sOps = benchHasSignal(true, S, Q, calls);
|
|
long fOps = benchHasSignal(false, S, Q, calls);
|
|
|
|
double speedup = (double) sOps / fOps;
|
|
boolean pass = sOps >= fOps * minSpeedup;
|
|
|
|
System.out.printf(" has_sig %-42s slow=%,d fast=%,d speedup=%.1fx %s%n",
|
|
name, sOps, fOps, speedup, pass ? "PASS" : "FAIL");
|
|
assert pass : String.format(
|
|
"has_signal %s: expected speedup >=%dx, got %.1fx", name, minSpeedup, speedup);
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("wasmer-0002: Signal Vec dedup");
|
|
System.out.println("=============================");
|
|
System.out.println("-- signal() delivery --");
|
|
|
|
// S=30 signals: after all 30 are in the set, each new deliver scans ~30 items
|
|
// vs O(1) bitmask. With N=1000 delivers cycling through 30: slow costs ~15000 ops.
|
|
testSignal("N=100 S=30 minSpeedup=10x", 100, 30, 10);
|
|
testSignal("N=1000 S=30 minSpeedup=10x", 1000, 30, 10);
|
|
|
|
System.out.println("-- has_signal() query --");
|
|
// S=30 pending, Q=10 query: slow=S*Q=300 per call, fast=Q+1=11 per call => ~27x
|
|
testHasSignal("S=30 Q=10 calls=100 minSpeedup=15x", 30, 10, 100, 15);
|
|
testHasSignal("S=30 Q=30 calls=100 minSpeedup=15x", 30, 30, 100, 15);
|
|
|
|
System.out.println("=============================");
|
|
System.out.println("ALL PASS");
|
|
}
|
|
}
|