testcafe-0001: Selector filterNodes (string-filter branch) and expandSelectorResults both dedup via Array.indexOf on growing result arrays. filterNodes: O(N*M) per selector filter. expandSelectorResults: O(N^2 * K^2) worst case when derivatives unique. Fix: Set<Node> keyed by object identity. Bench: 398x at N=2000 filter, 1966x at N=K=150 expand. webdriverio-0002: MSPO aggregator dedups per-test entries via Array.find on growing bucket array. O(N^2) per test bucket, same pattern repeats in unknown-suite merger. Fix: companion Map<bucketKey, Set<selector>> for O(1) dedup. Bench: 493x at N=2000. UNDF IDs: 1290 (testcafe), 1291 (webdriverio-0002). All 17 tests pass.
110 lines
3.5 KiB
Python
110 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
# bench-testcafe-0001.py
|
|
# Selector.filterNodes: matchingArr.indexOf(node) > -1 per node (O(N*M))
|
|
# vs matchingSet.has(node) (O(N+M)).
|
|
# Selector.expandSelectorResults: result.indexOf(deriv) < 0 on growing result
|
|
# (worst O(N*K*(N*K))) vs parallel seen Set (O(N*K)).
|
|
|
|
import sys
|
|
import time
|
|
|
|
|
|
class Node:
|
|
"""Unique object acting as DOM Node for identity-based dedup."""
|
|
__slots__ = ("id",)
|
|
def __init__(self, i):
|
|
self.id = i
|
|
|
|
|
|
def bench_filter_defective(n, m):
|
|
"""matchingArr.indexOf(node) > -1 per node."""
|
|
matching = [Node(i) for i in range(m)]
|
|
nodes = matching + [Node(1000 + i) for i in range(n - m)] # N total, M match
|
|
|
|
t0 = time.perf_counter()
|
|
matchingArr = [x for x in matching]
|
|
# lambda filter captures matchingArr
|
|
filter_fn = lambda node: matchingArr.index(node) > -1 if node in matchingArr else False
|
|
# simpler: node in matchingArr (list) = O(M) per check
|
|
result = []
|
|
for node in nodes:
|
|
if node in matchingArr:
|
|
result.append(node)
|
|
return time.perf_counter() - t0
|
|
|
|
|
|
def bench_filter_fixed(n, m):
|
|
"""matchingSet.has(node)."""
|
|
matching = [Node(i) for i in range(m)]
|
|
nodes = matching + [Node(1000 + i) for i in range(n - m)]
|
|
|
|
t0 = time.perf_counter()
|
|
matchingSet = set(matching)
|
|
result = []
|
|
for node in nodes:
|
|
if node in matchingSet:
|
|
result.append(node)
|
|
return time.perf_counter() - t0
|
|
|
|
|
|
def bench_expand_defective(n, k):
|
|
"""result.indexOf(deriv) < 0 against growing result."""
|
|
# All derivatives unique so result grows to N*K
|
|
nodes = [Node(i) for i in range(n)]
|
|
derivatives_per_node = [[Node(i * 10000 + j) for j in range(k)] for i in range(n)]
|
|
|
|
t0 = time.perf_counter()
|
|
result = []
|
|
for i in range(n):
|
|
for deriv in derivatives_per_node[i]:
|
|
if deriv not in result: # O(|result|)
|
|
result.append(deriv)
|
|
return time.perf_counter() - t0
|
|
|
|
|
|
def bench_expand_fixed(n, k):
|
|
"""Parallel seen Set."""
|
|
nodes = [Node(i) for i in range(n)]
|
|
derivatives_per_node = [[Node(i * 10000 + j) for j in range(k)] for i in range(n)]
|
|
|
|
t0 = time.perf_counter()
|
|
seen = set()
|
|
result = []
|
|
for i in range(n):
|
|
for deriv in derivatives_per_node[i]:
|
|
if deriv not in seen:
|
|
seen.add(deriv)
|
|
result.append(deriv)
|
|
return time.perf_counter() - t0
|
|
|
|
|
|
TRIALS = 3
|
|
FILTER_CASES = [(100, 50), (500, 250), (1000, 500), (2000, 1000)]
|
|
EXPAND_CASES = [(20, 20), (50, 50), (100, 100), (150, 150)]
|
|
|
|
|
|
def run():
|
|
lines = []
|
|
h = "=== testcafe-0001 filterNodes: indexOf vs Set.has ==="
|
|
print(h); lines.append(h)
|
|
for n, m in FILTER_CASES:
|
|
d = min(bench_filter_defective(n, m) for _ in range(TRIALS))
|
|
f = min(bench_filter_fixed(n, m) for _ in range(TRIALS))
|
|
speedup = (d / f) if f > 0 else float("inf")
|
|
l = f"N={n:<5} M={m:<5}: defective={d*1000:.3f}ms fixed={f*1000:.3f}ms speedup={speedup:.1f}x"
|
|
print(l); lines.append(l); sys.stdout.flush()
|
|
|
|
h = "=== testcafe-0001 expandSelectorResults: indexOf vs Set.has ==="
|
|
print(h); lines.append(h)
|
|
for n, k in EXPAND_CASES:
|
|
d = min(bench_expand_defective(n, k) for _ in range(TRIALS))
|
|
f = min(bench_expand_fixed(n, k) for _ in range(TRIALS))
|
|
speedup = (d / f) if f > 0 else float("inf")
|
|
l = f"N={n:<4} K={k:<4} (total {n*k}): defective={d*1000:.3f}ms fixed={f*1000:.3f}ms speedup={speedup:.1f}x"
|
|
print(l); lines.append(l); sys.stdout.flush()
|
|
|
|
return lines
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run()
|