[TwigComponent] Fix quadratic scanning in TwigPreLexer::consume() - #3775
Open
Kocal wants to merge 1 commit into
Open
[TwigComponent] Fix quadratic scanning in TwigPreLexer::consume()#3775Kocal wants to merge 1 commit into
Kocal wants to merge 1 commit into
Conversation
| Q | A
| -------------- | ---
| Bug fix? | no
| New feature? | no
| Deprecations? | no
| Documentation? | no
| Issues | -
| License | MIT
`consume()` built a copy of the whole remaining template on every call via
`substr()`, and it is called several times per character in the main scan
loop. Pre-lexing was therefore quadratic in template size.
Reuse `check()`, which compares in place with `substr_compare()`.
Pre-lexing a 154 KB template goes from ~724 ms to ~94 ms, and scaling is
now linear.
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;
$chunk = <<<'TWIG'
<div class="card">
<h1>Some title here</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.</p>
{% if foo %}
<span>{{ bar|upper }}</span>
{% endif %}
<twig:Alert type="success" :count="items|length">
Hello world
</twig:Alert>
</div>
TWIG;
$input = str_repeat($chunk, 500); // ~154 KB
(new TwigPreLexer())->preLexComponents($input);
```
Blackfire:
- before — 3.34s wall / 3.18s CPU: https://blackfire.io/profiles/8ef533c9-e96a-4b48-8f04-2711c8749ee6/graph
- after — 2.22s wall / 2.19s CPU: https://blackfire.io/profiles/a7ccabce-35d6-4163-988d-27ca37a439b4/graph
- diff: https://app.blackfire.io/profiles/compare/8ef533c9-e96a-4b48-8f04-2711c8749ee6...a7ccabce-35d6-4163-988d-27ca37a439b4/graph
Analysis, implementation and benchmarks by Claude Opus 5.
Kocal
force-pushed
the
perf/twig-component-prelexer-consume
branch
from
August 15, 2026 05:00
780feed to
dda63f6
Compare
smnandre
approved these changes
Aug 15, 2026
smnandre
left a comment
Member
There was a problem hiding this comment.
I'm a bit 😄 at the title .. compared to the IRL effects*, but this goes into the good direction so 👍
(* in production: not a nanosecond of difference, as templates are pre-compiled once during deploy, the average template is probably more around 5kb, and I doubt even 5% of them would even be concerned by this case.)
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.
consume()built a copy of the whole remaining template on every call viasubstr(), and it is called several times per character in the main scanloop. Pre-lexing was therefore quadratic in template size.
Reuse
check(), which compares in place withsubstr_compare().Pre-lexing a 154 KB template goes from ~724 ms to ~94 ms, and scaling is
now linear.
Benchmarked from the repository root with
blackfire run symfony php bench.php:Blackfire:
Analysis, implementation and benchmarks by Claude Opus 5.