Repository navigation
perf: regex hot-path optimizations — ~2x throughput improvement - #1905
Merged
Merged
Conversation
This was referenced Oct 27, 2026
Eliminate unnecessary ThreadLocal operations, object allocations and redundant method calls on the regex match hot path: - Short-circuit emitWarningsOnUse() when warningsOnUse list is empty - Guard ACTIVE_DEBUG_MODE save/restore with lexicalDebugMode != 0 check - Defer RuntimeList allocation to LIST context only (SCALAR skips it) - Eliminate double resolveRegex call between matchRegex and matchRegexDirect - Pre-allocate and reuse Joni captures Region instead of allocating per match - Use matcherNoRegion to skip unnecessary Joni-level Region allocation - Skip getQuotedRegexInPackage ThreadLocal save/restore when values unchanged Benchmark ($i =~ /42/ in 400-iteration loop, 100k times): Before: ~10,453/s After: ~17,430/s (+67%) Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Additional hot-path optimizations on top of the first round: - Eliminate Integer boxing in callsite warning state ThreadLocals by using int[]/String[] holder arrays (one allocation per thread) - Use callsiteId directly as cache key in getQuotedRegex, removing the string-concatenation indirection and extra HashMap lookup - Pass regexState to updateLastNamedCaptureGroups and updateNumberedCaptureGroups to avoid redundant state() ThreadLocal lookups - Cache regexState in updateReplacementMatchState to avoid 8 redundant state() calls Benchmark ($i =~ /42/ in 400-iteration loop, 100k times): Baseline: ~10,453/s Round 1: ~17,430/s (+67%) Round 2: ~21,000/s (+101% total, gap vs Perl 5 narrowed from 3.8x to 1.9x) Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Skip expensive buildCharToByte/buildByteToChar for ASCII-only strings (bytes.length == input.length implies identity mapping) - Inline warningsOnUse.isEmpty() check at call sites to avoid method call overhead when warning list is empty (common case) - Check regex callsite cache before reading package/lifecycle ThreadLocals in getQuotedRegexInPackage, saving 2 TL reads per match on cache hit Benchmark ($i =~ /42/ in 400-iteration loop, 100k times): Baseline: ~10,453/s Round 1: ~17,430/s (+67%) Round 2: ~21,000/s (+101%) Round 3: ~22,100/s (+111% total, gap vs Perl 5: 1.8x) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
matcherNoRegion passes null for the Region, so Matcher.captureBegin(n) for n > 0 calls getEagerRegion() which creates a minimal Region with only group 0 — $1 and higher are silently lost. Use regex.matcher(bytes) (with Region) when captureCount > 0; keep the faster matcherNoRegion path for patterns with no captures. Adds regression test regex_define_capture.t covering recursive DEFINE patterns and simple multi-group captures. Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fglock
force-pushed
the
feature/regex-perf-hot-path
branch
from
October 27, 2026 16:51
6190a82 to
ad62884
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reduces per-match regex overhead in the hot path (
$i =~ /42/benchmark).Async-profiler showed the Joni regex engine was only ~2% of CPU — the gap
was in runtime plumbing: ThreadLocal operations, object allocations, redundant
lookups.
Round 1 — +67% throughput
emitWarningsOnUsewhen warning list is empty (saves 4 ThreadLocal reads/match)ACTIVE_DEBUG_MODEsave/restore behindlexicalDebugMode != 0(saves 2 ThreadLocal ops/match)RuntimeListallocation to LIST context only (eliminates allocation in SCALAR path)resolveRegexcall inmatchRegex→matchRegexDirectmatcherNoRegionand pre-allocatecapturesRegion inJoniRegexPatternRound 2 — +101% total throughput
int[]/String[]holders)callsiteIddirectly as regex cache key, removing string-concatenation and extra HashMap lookupregexStateto capture-group update methods to avoid redundantstate()ThreadLocal callsregexStateinupdateReplacementMatchState(was callingstate()8+ times)Round 3 — +111% total throughput
buildCharToByte/buildByteToCharfor ASCII-only strings (identity mapping)warningsOnUse.isEmpty()check at call sites to avoid method call overheadBenchmark results
dev/bench/benchmark_regex.pl—$i =~ /42/in 400-iteration loop, 100k calls:Test plan
make— all unit tests pass (all 3 rounds)Closes #1904
🤖 Generated with Claude Code