Compile inflector regexes once instead of per call - #54
Open
perryqh wants to merge 1 commit into
Open
Conversation
`camelize` and `to_class_case` run once per file while the Zeitwerk constant map is built -- roughly 50k calls per invocation on a large codebase -- and each call was compiling between two and seven regexes from scratch. Building a regex constructs a DFA, so this was not a marginal cost. Hoisted every pattern into a `LazyLock<Regex>` compiled once per process. MEASURED on a 51,513-file app: wall clock 5.127s -> 4.656s (-9.2%) user cpu time 13.561s -> 7.370s (-45.7%) "infer constants from filename" phase 0.689s -> <0.12s Wall clock improves less than CPU time because this phase is parallelized across cores, so the wasted work was partly hidden. It was still burning nearly half the process's total CPU. Behavior is deliberately unchanged, including one oddity: the "Statuss" replacement has always had its result discarded, so "statuss" is left alone. That is preserved verbatim with a comment, and the existing test that pins it still passes. It looks like a bug, but fixing it belongs in its own change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
perryqh
force-pushed
the
perf/oncelock-regexes
branch
from
August 19, 2026 22:28
d278f48 to
a6ab8fd
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.
Best return per line of any change I've measured on
pks check: ~40 lines, and it cuts total CPU time by 45%.Note
Stacked on #53 (which is itself stacked on #52). The diff above shows only this change — one file. GitHub retargets the base automatically as each parent merges.
The problem
camelizeandto_class_caserun once per file while the Zeitwerk constant map is built — roughly 50k calls per invocation on a large codebase. Each call was compiling between two and seven regexes from scratch:Regex::newconstructs a DFA. Doing that per filename, for every file in the app, on every run, is not a marginal cost.The change
Every pattern becomes a
LazyLock<Regex>, compiled once per process. That's the whole change — one file, no API or behavior change.Measured
On a 51,513-file application:
Two honest notes on those numbers:
One thing to know about the diff
This preserves an existing oddity rather than fixing it:
That result has always been thrown away, so
statussis left untouched — andtest_to_class_casepins that behavior with a("statuss", false, "Statuss")case. I kept it verbatim with a comment explaining why. It looks like a bug, but fixing it changes inflection output and belongs in its own PR, not one whose only job is to stop recompiling regexes.Two other files have the same compile-per-call pattern (
file_utils.rs:108,rails_utils.rs:19). I left them alone: once this landed, the phase was down to 0.047 s and there was nothing left to win. Not worth the churn.Verification
cargo test— 257 passingcargo clippy --all-targets --all-features— cleancargo fmt --all -- --check— cleanHeads-up: a pre-existing flaky test
While verifying, I hit
gitignore_test::test_respect_gitignore_can_be_disabledfailing on roughly 1 run in 3. It is not caused by this change — it reproduces at the same rate on the base branch, and passes 6/6 when run in isolation.Cause:
common::teardown()globstests/fixtures/*/tmp/cache/packwerkand deletes the cache for every fixture, not just the one the calling test used.gitignore_test.rscalls it from 7 different tests, which Rust runs in parallel threads within the same binary — so one test deletes another's cache mid-run.Worth fixing separately (scope
teardownto the fixture the test actually touched). Flagging it here because it will make CI intermittently red regardless of this PR.🤖 Generated with Claude Code