initial upload
This commit is contained in:
103
regex-bench.py
Normal file
103
regex-bench.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
regex_mt_bench.py – does Python's re.search() scale across threads?
|
||||
|
||||
• Creates a single master regex (similar to the RegexValidator patch).
|
||||
• Launches N threads; each thread calls .search() a fixed number of times.
|
||||
• Reports wall-time vs process-CPU-time so you can see how many cores
|
||||
the regex engine actually used.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import regex as re, threading, time, os, sys, random, math
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 1. Prepare synthetic workload
|
||||
# ---------------------------------------------------------------------
|
||||
NUM_PATTERNS = 120 # similar to your real list
|
||||
ITERATIONS = 80_000 # 16× more work than before
|
||||
TEXT_LEN_CHARS = 800_000 # force the engine to read a lot
|
||||
|
||||
random.seed(42)
|
||||
|
||||
# Make deterministic-ish patterns: a literal word or a short .* wildcard
|
||||
_PATTERNS: list[str] = []
|
||||
for i in range(NUM_PATTERNS):
|
||||
if i % 3 == 0:
|
||||
_PATTERNS.append(fr"\bword{i}\b")
|
||||
elif i % 3 == 1:
|
||||
_PATTERNS.append(fr"phrase{i}[^ ]+end")
|
||||
else:
|
||||
_PATTERNS.append(fr"token{i}.*?token{i+1}")
|
||||
|
||||
# Build a master alternation with named groups (as in the patch)
|
||||
parts, _group2raw = [], {}
|
||||
for i, p in enumerate(_PATTERNS):
|
||||
gname = f"P{i}"
|
||||
parts.append(f"(?P<{gname}>{p})")
|
||||
_group2raw[gname] = p
|
||||
_BIG_RE = re.compile("|".join(parts), re.IGNORECASE | re.MULTILINE | re.DOTALL)
|
||||
|
||||
# Generate text that *sometimes* matches: sprinkle keywords every ~1000 chars
|
||||
_chunks = []
|
||||
for i in range(TEXT_LEN_CHARS // 50):
|
||||
if i % 20 == 0: # every 20th chunk drop a keyword
|
||||
k = random.randrange(NUM_PATTERNS)
|
||||
tok = f"word{k}" if k % 3 == 0 else f"phrase{k}xxend"
|
||||
_chunks.append(tok)
|
||||
else:
|
||||
_chunks.append("loremipsum")
|
||||
_TEXT = " ".join(_chunks)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 2. Benchmark helper
|
||||
# ---------------------------------------------------------------------
|
||||
def run_threads(n_threads: int) -> tuple[float, float]:
|
||||
"""
|
||||
Launch n_threads that each call _BIG_RE.search(_TEXT) ITERATIONS times.
|
||||
|
||||
Returns (wall_seconds, cpu_seconds) for the whole job.
|
||||
"""
|
||||
def worker():
|
||||
s = _BIG_RE # local var for speed
|
||||
t = _TEXT
|
||||
for _ in range(ITERATIONS):
|
||||
s.search(t)
|
||||
|
||||
threads = [threading.Thread(target=worker, daemon=True)
|
||||
for _ in range(n_threads)]
|
||||
|
||||
cpu_start = os.times() # returns a 5-tuple
|
||||
t0 = time.perf_counter()
|
||||
|
||||
for th in threads:
|
||||
th.start()
|
||||
for th in threads:
|
||||
th.join()
|
||||
|
||||
wall = time.perf_counter() - t0
|
||||
cpu = (os.times().user + os.times().system) - (cpu_start.user + cpu_start.system)
|
||||
return wall, cpu
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# 3. Run for several thread counts
|
||||
# ---------------------------------------------------------------------
|
||||
def main():
|
||||
print(f"patterns : {NUM_PATTERNS}")
|
||||
print(f"text length (chars) : {len(_TEXT):,}")
|
||||
print(f"regex searches/thread: {ITERATIONS}")
|
||||
print()
|
||||
|
||||
for n in (1, 2, 4, 8):
|
||||
wall, cpu = run_threads(n)
|
||||
util = cpu / wall if wall else math.nan
|
||||
print(f"{n:>2} threads → wall {wall:6.2f} s "
|
||||
f"CPU {cpu:6.2f} s ratio {util:4.2f}")
|
||||
|
||||
print("\nInterpretation:")
|
||||
print(" • ratio ≈ 1.0 → work is effectively single-core.")
|
||||
print(" • ratio → N → regex scanning scales across N cores.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user