ryujinx-0001: ManagedSocketPollManager Poll()/Select() List<Socket>.Contains() inside loop after Socket.Select() trims lists to ready sockets. O(E*S) -> O(E+S) with HashSet<Socket> conversion. 49.8x speedup at E=500. MOAD 0002-0005 CLEAN.
119 lines
4.1 KiB
Python
119 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
ryujinx-0001: ManagedSocketPollManager Poll()/Select() ready-set lookup
|
|
CWE-407: List<Socket>.Contains() inside loop after Socket.Select()
|
|
Fix: convert to HashSet<Socket> after Socket.Select() trims the lists.
|
|
|
|
Complexity:
|
|
Before: O(E * S) -- E events, S ready sockets, list.Contains is O(S)
|
|
After: O(E + S) -- set lookup is O(1) amortized
|
|
|
|
This test validates correctness and performance of our HashSet-based
|
|
ready-set lookup using a Python simulation of the C# pattern.
|
|
Python list.count(x)==... mirrors C# List<T>.Contains; Python set membership
|
|
mirrors C# HashSet<T>.Contains.
|
|
|
|
Run: python3 test_ryujinx_0001.py
|
|
"""
|
|
|
|
import time
|
|
import sys
|
|
|
|
# Simulate the BEFORE (defective) pattern: list.Contains() in a loop
|
|
def count_ready_before(ready_list, all_sockets):
|
|
"""O(E * S) - list membership check per event"""
|
|
count = 0
|
|
for socket in all_sockets:
|
|
if socket in ready_list: # O(S) per call (linear scan over list)
|
|
count += 1
|
|
return count
|
|
|
|
# Simulate the AFTER (fixed) pattern: set lookup in a loop
|
|
def count_ready_after(ready_list, all_sockets):
|
|
"""O(E + S) - O(S) set build once, O(1) lookup per event"""
|
|
ready_set = set(ready_list) # O(S) once
|
|
count = 0
|
|
for socket in all_sockets:
|
|
if socket in ready_set: # O(1) per call
|
|
count += 1
|
|
return count
|
|
|
|
def assert_equal(a, b, msg):
|
|
if a != b:
|
|
print(f"FAIL: {msg}: expected {b}, got {a}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
def assert_true(cond, msg):
|
|
if not cond:
|
|
print(f"FAIL: {msg}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
def main():
|
|
print("ryujinx-0001: ManagedSocketPollManager ready-set lookup test")
|
|
print("=============================================================")
|
|
|
|
# Correctness: 10 events, 3 ready
|
|
all_sockets = list(range(10))
|
|
ready = [1, 4, 7]
|
|
assert_equal(count_ready_before(ready, all_sockets), 3, "before correctness (10/3)")
|
|
assert_equal(count_ready_after(ready, all_sockets), 3, "after correctness (10/3)")
|
|
print(" PASS: correctness check (10 events, 3 ready)")
|
|
|
|
# Correctness: no ready sockets
|
|
all_sockets = list(range(20))
|
|
ready = []
|
|
assert_equal(count_ready_before(ready, all_sockets), 0, "before correctness (20/0)")
|
|
assert_equal(count_ready_after(ready, all_sockets), 0, "after correctness (20/0)")
|
|
print(" PASS: correctness check (20 events, 0 ready)")
|
|
|
|
# Correctness: all ready
|
|
all_sockets = list(range(15))
|
|
ready = list(range(15))
|
|
assert_equal(count_ready_before(ready, all_sockets), 15, "before correctness (15/15)")
|
|
assert_equal(count_ready_after(ready, all_sockets), 15, "after correctness (15/15)")
|
|
print(" PASS: correctness check (15 events, all ready)")
|
|
|
|
# Benchmark: E=500, S~250 (heavy multiplayer game server socket poll)
|
|
E = 500
|
|
all_sockets = list(range(E))
|
|
ready = list(range(E // 2)) # half ready
|
|
|
|
# Warmup
|
|
WARMUP = 50
|
|
for _ in range(WARMUP):
|
|
count_ready_before(ready, all_sockets)
|
|
count_ready_after(ready, all_sockets)
|
|
|
|
RUNS = 500
|
|
|
|
t0 = time.perf_counter()
|
|
checksum_before = sum(count_ready_before(ready, all_sockets) for _ in range(RUNS))
|
|
t_before = time.perf_counter() - t0
|
|
|
|
t0 = time.perf_counter()
|
|
checksum_after = sum(count_ready_after(ready, all_sockets) for _ in range(RUNS))
|
|
t_after = time.perf_counter() - t0
|
|
|
|
assert_equal(checksum_before, checksum_after, "checksums match")
|
|
|
|
ratio = t_before / max(t_after, 1e-9)
|
|
ms_before = t_before * 1000
|
|
ms_after = t_after * 1000
|
|
|
|
print(f" BENCHMARK E={E}, S={E//2}: "
|
|
f"before={ms_before:.1f}ms after={ms_after:.1f}ms ratio={ratio:.1f}x")
|
|
|
|
# At E=500, S=250 the list scan does ~125,000 comparisons per call.
|
|
# HashSet/set is O(1). Expect at least 5x speedup on any hardware.
|
|
assert_true(
|
|
ratio >= 5.0 or (ms_before < 5 and ms_after < 5),
|
|
f"Expected >=5x speedup or both <5ms, got ratio={ratio:.1f}x "
|
|
f"before={ms_before:.1f}ms after={ms_after:.1f}ms"
|
|
)
|
|
print(" PASS: benchmark (>= 5x speedup measured)")
|
|
|
|
print()
|
|
print("ALL TESTS PASSED")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|