Skip to content

perf: accelerate safe API hot paths - #100

Merged
Qubitium merged 38 commits into
mainfrom
devin/api-speedups
Aug 10, 2026
Merged

perf: accelerate safe API hot paths#100
Qubitium merged 38 commits into
mainfrom
devin/api-speedups

Conversation

@Qubitium

@Qubitium Qubitium commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR targets safe API-level speedups after PR #99, preserving CPython 3.10 compatibility and free-threaded CPython 3.14t/GIL=0 behavior.

  • Adds allocation-free C fast-call entry points for default findall, substitute, finditer, and split shapes.
  • Releases the GIL for the first large findall scan while retaining worker-local PCRE2 state and the owned subject; subsequent match/object construction stays protected.
  • Makes ASCII span/start/end/regs offset conversion constant-time instead of rescanning the UTF-8 prefix.
  • Makes literal Match.expand constant-time and adds a bounded, clearable LRU for immutable replacement-template parsing/conversion.
  • Skips Python replacement-template parsing when a replacement is provably literal, while retaining the existing PCRE2 conversion path for backreferences and escaped syntax.
  • Batches parallel_map work into ordered chunks, reducing Future allocation and queue-lock overhead without changing result order or exception propagation.
  • Uses macOS hw.perflevel0.logicalcpu to permit safe explicit parallelism across the 12-logical performance cluster while retaining conservative fallbacks elsewhere.
  • Uses a bounded canonical-pattern layer for exact default module-level calls and dispatches directly to the C hot paths for match, search, fullmatch, findall, finditer, split, and literal sub/subn.
  • Adds an immutable per-match cache for the default Match.groups() tuple; custom defaults remain fresh and do not retain caller objects.
  • Caches immutable Match.expand() template parsing by template, capture count, and a snapshot of the current name table; rendering remains per-match and per-call.
  • Adds a per-thread bounded canonical cache for exact default compile(str|bytes) calls, preserving thread-local wrapper ownership and cache-limit semantics.
  • Extends the same bounded direct path to exact built-in patterns with plain integer flags, including JIT/compatibility keys and explicit thread markers; project Flag enums remain on the hardened general cache for free-threaded safety.
  • Avoids executor creation for one-item parallel_map calls while retaining the list-shaped API and auto-mode subject validation.
  • Inlines up to eight short canonical C subjects for explicit Flag.THREADS, avoiding queue setup while leaving auto-threshold behavior unchanged.
  • Adds a direct C fast path for default immutable bound Pattern.split calls; subclass, buffer, and bounded-limit paths remain unchanged.
  • Adds the matching exact-type fast path for default literal bound Pattern.subn; escaped, callable, subclass, buffer, and bounded-count replacements remain on the compatibility path.
  • Adds construction-validated plain-literal fast paths for bound and module-level findall, split, and sub/subn; regex metacharacters, explicit flags, subclasses, buffers, and negative-count semantics stay on the compatibility path.
  • Routes bounded module-level literal subn calls through the same count-correct bound fast path; the existing PCRE2 substitute entry point remains unchanged for non-literal patterns.
  • Adds vectorcall and immutable-snapshot fast paths for hot Match APIs (group, span, start, end, and regs).
  • Adds compatibility, large-scan, replacement-cache, vectorcall, batching, module-dispatch, and Python 3.10/3.14t coverage tests.
  • Adds reproducible benchmarks/api_hotpaths.py and benchmarks/parallel_map_hotpath.py measurements.

Performance discovery

Pinned A/B runs use the same macOS taskpolicy -t 1 -l 1 scheduler policy. This host reports 12 performance logical CPUs and 4 efficiency logical CPUs; macOS does not expose an unprivileged hard per-process CPU mask, so the benchmark records the topology and uses the same performance-tier policy for both runs.

  • Canonical module-level calls on short subjects now measure (Python 3.10 / Python 3.14t, in microseconds): match 0.408 / 0.338, search 0.412 / 0.342, fullmatch 0.427 / 0.351, findall 0.639 / 0.558, finditer 0.774 / 0.792, split 0.613 / 0.576, and literal sub 0.961 / 0.871. This is about 2.5–3.1x faster than the pre-dispatch module hot path and compounds with the earlier C speedups.
  • Bound backreference substitution improved from about 5.0 us to 1.38 us on Python 3.10 (about 3.5x) and 1.14 us on Python 3.14t.
  • Match.groups() with its default argument is now an immutable per-match tuple cache; repeated calls measure about 0.019 us on Python 3.10 and 0.022 us on Python 3.14t.
  • Repeated Match.expand(r"[\\1]") measures about 0.94 us on Python 3.10 and 0.70 us on Python 3.14t after eliminating repeated parser and piece-list work.
  • Repeated default compile("(x)") measures about 0.34 us on Python 3.10 and 0.25 us on Python 3.14t after avoiding general cache-key and wrapper dispatch overhead.
  • Repeated integer-flagged compile("x", CASELESS) measures about 0.84 us on Python 3.10 and 0.58 us on Python 3.14t.
  • One-item parallel_map(findall) avoids executor setup (13.3x faster on Python 3.10 and 27.7x on Python 3.14t in the measured microbench).
  • Tiny explicit maps up to eight subjects also execute inline; larger maps retain ordered bounded batching and worker fan-out.
  • Default bound Pattern.split measures about 0.35 / 0.33 us on Python 3.10 / 3.14t versus about 0.59 / 0.50 us through the compatibility path.
  • Default bound literal Pattern.subn measures about 0.43 us on Python 3.10, down from about 0.64 us before the shortcut.
  • Plain-literal findall measures about 2.0 us versus 19.3 us on a 1,000-byte repeated-token subject, and about 301 us versus 2.4 ms on delimiter-heavy text; the no-match first-character short-circuit is also faster than PCRE2 scanning.
  • Plain-literal subn measures about 1.1 us on a 1,000-byte repeated-token subject versus about 15.8 us through the prior C path, and about 0.8 ms versus 2.4 ms on delimiter-heavy text.
  • parallel_map(search), 16 one-million-character subjects, 12 workers: 8.57x on Python 3.10 and 7.85x on Python 3.14t/GIL=0.
  • parallel_map(findall), 48 one-million-character subjects, 12 workers: 11.51x on Python 3.10 and 11.25x on Python 3.14t/GIL=0.
  • Focused free-threaded lookup fan-out is roughly 1.5–2x faster for short match/search/fullmatch/findall calls after owner-aware C vectorcall dispatch.

Already PCRE2-dominated bound match/search calls remain near their C floor; they are left unchanged where extra dispatch would trade away accuracy or safety.

Safety and validation

  • Python 3.10 full suite: 489 passed, 18 skipped.
  • Python 3.14t/GIL=0 full suite: 489 passed, 15 skipped, 545 subtests.
  • Python package statement and branch coverage: 100% (1,415 statements, 564 branches across the six public modules).
  • Focused C API, memory, thread, and free-threaded tests pass on both interpreters.
  • ruff format --check and git diff --check pass; the extension builds cleanly for both interpreters.

All fast paths preserve type checks, PCRE2 replacement escaping, match ownership, ordered results, exception behavior, and thread-safe resource handling. The large-scan path releases the GIL only around PCRE2 work and never exposes mutable Python state to the matcher.

@Qubitium
Qubitium marked this pull request as ready for review August 9, 2026 06:51
@Qubitium
Qubitium merged commit 3bfd23a into main Aug 10, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant