Skip to content

[Translator] Cache the RegExp compiled by strtr() - #3784

Open
Kocal wants to merge 1 commit into
symfony:3.xfrom
Kocal:perf/translator-strtr-regex-cache
Open

[Translator] Cache the RegExp compiled by strtr()#3784
Kocal wants to merge 1 commit into
symfony:3.xfrom
Kocal:perf/translator-strtr-regex-cache

Conversation

@Kocal

@Kocal Kocal commented Aug 15, 2026

Copy link
Copy Markdown
Member
Q A
Bug fix? no
New feature? no
Deprecations? no
Documentation? no
Issues -
License MIT

strtr() built and compiled a new RegExp on every call, and it is called once per trans() with parameters. The pattern only depends on the parameter names, which come from a fixed set in the source code.

Cache the compiled RegExp by pattern and hoist the escaping regex out of the mapping callback.

200k trans() calls: ~226 ms -> ~195 ms with parameters, ~214 ms -> ~189 ms with plural forms.

Browser-side JavaScript, so there is no Blackfire profile for this one. Benchmarked on node 22, after pnpm --filter @symfony/ux-translator run build, from the repository root with node bench.mjs:

import { createTranslator } from './src/Translator/assets/dist/translator_controller.js';

const messages = {
    'app.greeting': { id: 'app.greeting', translations: { messages: { en: 'Hello %name%, you have %count% new messages' } } },
    'app.plural': { id: 'app.plural', translations: { messages: { en: '{0} No apples|one: One apple|more: %count% apples' } } },
};

const translator = createTranslator({ messages, locale: 'en' });
const n = 200000;

let start = process.hrtime.bigint();
for (let i = 0; i < n; i++) {
    translator.trans('app.greeting', { '%name%': 'Hugo', '%count%': i });
}
console.log(`simple  ${n} trans() -> ${Number(process.hrtime.bigint() - start) / 1e6} ms`);

start = process.hrtime.bigint();
for (let i = 0; i < n; i++) {
    translator.trans('app.plural', { '%count%': i % 7 });
}
console.log(`plural  ${n} trans() -> ${Number(process.hrtime.bigint() - start) / 1e6} ms`);

Analysis, implementation and benchmarks by Claude Opus 5.

@Kocal Kocal self-assigned this Aug 15, 2026
@carsonbot carsonbot added the Status: Needs Review Needs to be reviewed label Aug 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📊 Packages dist files size difference

Thanks for the PR! Here is the difference in size of the packages dist files between the base branch and the PR.
Please review the changes and make sure they are expected.

FileBefore (Size / Gzip)After (Size / Gzip)
Translator
translator_controller.js 7.17 kB / 2.19 kB 7.39 kB+3% 📈 / 2.29 kB+5% 📈

@Kocal
Kocal requested review from kbond and smnandre August 15, 2026 05:32
@Kocal
Kocal force-pushed the perf/translator-strtr-regex-cache branch from 628fa51 to 9533ce1 Compare August 15, 2026 22:10
| Q              | A
| -------------- | ---
| Bug fix?       | no
| New feature?   | no
| Deprecations?  | no
| Documentation? | no
| Issues         | -
| License        | MIT

`strtr()` built and compiled a new `RegExp` on every call, and it is
called once per `trans()` with parameters. The pattern only depends on the
parameter names, which come from a fixed set in the source code.

Cache the compiled `RegExp` by pattern and hoist the escaping regex out of
the mapping callback.

200k `trans()` calls: ~226 ms -> ~195 ms with parameters, ~214 ms -> ~189 ms
with plural forms.

Browser-side JavaScript, so there is no Blackfire profile for this one.
Benchmarked on node 22, after `pnpm --filter @symfony/ux-translator run
build`, from the repository root with `node bench.mjs`:

```js
import { createTranslator } from './src/Translator/assets/dist/translator_controller.js';

const messages = {
    'app.greeting': { id: 'app.greeting', translations: { messages: { en: 'Hello %name%, you have %count% new messages' } } },
    'app.plural': { id: 'app.plural', translations: { messages: { en: '{0} No apples|one: One apple|more: %count% apples' } } },
};

const translator = createTranslator({ messages, locale: 'en' });
const n = 200000;

let start = process.hrtime.bigint();
for (let i = 0; i < n; i++) {
    translator.trans('app.greeting', { '%name%': 'Hugo', '%count%': i });
}
console.log(`simple  ${n} trans() -> ${Number(process.hrtime.bigint() - start) / 1e6} ms`);

start = process.hrtime.bigint();
for (let i = 0; i < n; i++) {
    translator.trans('app.plural', { '%count%': i % 7 });
}
console.log(`plural  ${n} trans() -> ${Number(process.hrtime.bigint() - start) / 1e6} ms`);
```

Analysis, implementation and benchmarks by Claude Opus 5.
@Kocal
Kocal force-pushed the perf/translator-strtr-regex-cache branch from 9533ce1 to d2f272a Compare August 15, 2026 22:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants