Repository navigation
Cache compiled interpolation patterns across calls - #450
Merged
whatyouhide merged 1 commit intoOct 27, 2026
Merged
Conversation
to_interpolatable/1 compiled the "%{" and "}" patterns with
:binary.compile_pattern/1 on every call. Each compilation costs about as
much as splitting a short string, and this function runs on every runtime
interpolation: whenever the current locale has no translation for a
message, including every render in the default locale of an application
without a PO file for it, and once per message at compile time.
A compiled pattern is a reference, so it cannot live in a module attribute;
the two patterns are now compiled on first use and kept in persistent_term.
Passing the literal strings to :binary.split/2 instead would compile them
again on every split and gets slower as the number of bindings grows.
Microbenchmark of runtime_interpolate/2 (compiled loop, median of 7
alternating samples, Elixir 1.20.4 / OTP 29, one scheduler; OTP 27 within
5% of these):
input before after
no bindings 0.44 us 0.15 us
1 binary binding 0.72 us 0.39 us
3 binary bindings 1.06 us 0.78 us
32 bindings 7.76 us 7.27 us
long text, 1 binding 0.75 us 0.45 us
josevalim
approved these changes
Oct 26, 2026
Contributor
|
Thank you @oliver-kriska 🫶 |
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.
Reuse compiled interpolation patterns
Cache the compiled
"%{"and"}"split patterns in:persistent_termrather than compiling them on everyto_interpolatable/1call. This speeds up runtime interpolation when a translation is missing, including source-language messages without a translated entry, and avoids the same repeated work during compile-time interpolation.Split from #445 as requested, based directly on the latest upstream
main, independently of the POT-read and plural-info changes. The merged literal-%{}fix from #444 is preserved.A compiled pattern contains a reference and cannot be stored in a module attribute. Literal split patterns were measured and rejected because they recompile on each split and become slower with more bindings.
The original lazy initialization is unchanged: concurrent first callers may compile and store different valid pattern tuples. Correctness is unaffected, but replacement
:persistent_term.put/2calls can trigger global heap scans. This does not guarantee exactly one compilation or write on a cold VM; subsequent calls reuse the stored patterns.Prior measurements
These are the corrected historical measurements from #445, not new measurements against today's
main: Elixir 1.20.4/OTP 29, one scheduler,MIX_ENV=prod, compiled benchmark loop, median of seven alternating samples ofruntime_interpolate/2.Verification on the new base
--warnings-as-errorsand formatting checks pass.%{}prefixes and missing-binding results are preserved. This checks correctness, not startup write count or latency.