Skip to content

[TwigComponent] Reduce per-character work in the pre-lexer scan loops - #3776

Open
Kocal wants to merge 1 commit into
symfony:3.xfrom
Kocal:perf/twig-component-prelexer-scan-loop
Open

[TwigComponent] Reduce per-character work in the pre-lexer scan loops#3776
Kocal wants to merge 1 commit into
symfony:3.xfrom
Kocal:perf/twig-component-prelexer-scan-loop

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

consumeUntilEndBlock() ran six substr() comparisons on every character
of a block body, and the main loop ran a preg_match() per character to
test for whitespace.

Guard the delimiter comparisons behind the only three characters that can
start one ({, <, #), and swap the regex for ctype_space().

Pre-lexing a 167 KB template made of components with blocks goes from
~64 ms to ~16 ms.

Benchmarked from the repository root with blackfire run symfony php bench.php:

<?php
require __DIR__.'/src/TwigComponent/vendor/autoload.php';

use Symfony\UX\TwigComponent\Twig\TwigPreLexer;

$body = str_repeat("    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\n", 40);
$chunk = "<twig:Card>\n    {% block header %}\n{$body}\n    {% endblock %}\n</twig:Card>\n\n";

$input = str_repeat($chunk, 60); // ~167 KB
(new TwigPreLexer())->preLexComponents($input);

Blackfire:

Analysis, implementation and benchmarks by Claude Opus 5.

| Q              | A
| -------------- | ---
| Bug fix?       | no
| New feature?   | no
| Deprecations?  | no
| Documentation? | no
| Issues         | -
| License        | MIT

`consumeUntilEndBlock()` ran six `substr()` comparisons on every character
of a block body, and the main loop ran a `preg_match()` per character to
test for whitespace.

Guard the delimiter comparisons behind the only three characters that can
start one (`{`, `<`, `#`), and swap the regex for `ctype_space()`.

Pre-lexing a 167 KB template made of components with blocks goes from
~64 ms to ~16 ms.

Benchmarked from the repository root with `blackfire run symfony php bench.php`:

```php
<?php
require __DIR__.'/src/TwigComponent/vendor/autoload.php';

use Symfony\UX\TwigComponent\Twig\TwigPreLexer;

$body = str_repeat("    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>\n", 40);
$chunk = "<twig:Card>\n    {% block header %}\n{$body}\n    {% endblock %}\n</twig:Card>\n\n";

$input = str_repeat($chunk, 60); // ~167 KB
(new TwigPreLexer())->preLexComponents($input);
```

Blackfire:

- before — 1.06s wall / 1.05s CPU: https://blackfire.io/profiles/d4ce5972-9eb7-4796-beac-5f1f5921b5db/graph
- after — 72.1ms wall / 71.2ms CPU: https://blackfire.io/profiles/fe19e6f8-5d57-4d42-b270-6e741c87af20/graph
- diff: https://app.blackfire.io/profiles/compare/d4ce5972-9eb7-4796-beac-5f1f5921b5db...fe19e6f8-5d57-4d42-b270-6e741c87af20/graph

Analysis, implementation and benchmarks by Claude Opus 5.
@Kocal Kocal self-assigned this Aug 15, 2026
@carsonbot carsonbot added Bug Bug Fix Deprecation Documentation Improvements or additions to documentation Feature New Feature TwigComponent Status: Needs Review Needs to be reviewed labels Aug 15, 2026
@Kocal Kocal added Performance and removed Bug Bug Fix Documentation Improvements or additions to documentation Feature New Feature Deprecation labels Aug 15, 2026
@Kocal
Kocal requested review from kbond and smnandre August 15, 2026 05:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants