Skip to content

[TwigComponent] Fix quadratic scanning in TwigPreLexer::consume() - #3775

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

[TwigComponent] Fix quadratic scanning in TwigPreLexer::consume()#3775
Kocal wants to merge 1 commit into
symfony:3.xfrom
Kocal:perf/twig-component-prelexer-consume

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

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
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:

Analysis, implementation and benchmarks by Claude Opus 5.

@Kocal Kocal self-assigned this Aug 15, 2026
| 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
Kocal force-pushed the perf/twig-component-prelexer-consume branch from 780feed to dda63f6 Compare August 15, 2026 05:00
@Kocal
Kocal requested review from kbond and smnandre August 15, 2026 05:27

@smnandre smnandre left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

@carsonbot carsonbot added Status: Reviewed Has been reviewed by a maintainer and removed Status: Needs Review Needs to be reviewed labels Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Performance Status: Reviewed Has been reviewed by a maintainer TwigComponent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants