From 02d672fab8ec2db7b173723a372e4d423f235904 Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Fri, 14 Aug 2026 22:47:39 +0200 Subject: [PATCH] [TwigComponent] Fix quadratic scanning in TwigPreLexer::consume() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | 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. Compare in place with `substr_compare()` instead. Delegating to the existing `check()` helper was the obvious move but measured slower on small templates, where the extra call costs more than the copy it saves, so the bounds check is inlined. Scaling is now linear, and no template size is slower than before: 185 B 89 us -> 89 us 925 B 460 us -> 444 us 7.4 KB 4649 us -> 3557 us 55 KB 92.1 ms -> 27.4 ms Pre-lexing every Twig template of this repository, 390 files with a median size of 332 B: ~23.2 ms -> ~19.6 ms. Benchmarked from the repository root with `blackfire run symfony php bench.php`: ```php

Some title here

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.

{% if foo %} {{ bar|upper }} {% endif %} Hello world TWIG; $input = str_repeat($chunk, 500); // ~154 KB (new TwigPreLexer())->preLexComponents($input); ``` Blackfire: - before — 3214ms wall / 3071ms CPU: https://app.blackfire.io/envs/5f4f9a62-eaa0-45ee-b7b3-a1b879f550e9/profiles/80e2c39f-32d1-441c-8a30-68782efbce21/graph - after — 2213ms wall / 2204ms CPU: https://app.blackfire.io/envs/5f4f9a62-eaa0-45ee-b7b3-a1b879f550e9/profiles/9eba2d4c-b3a3-4417-bcb3-f6a488857de2/graph - diff: https://app.blackfire.io/envs/5f4f9a62-eaa0-45ee-b7b3-a1b879f550e9/profiles/compare/80e2c39f-32d1-441c-8a30-68782efbce21...9eba2d4c-b3a3-4417-bcb3-f6a488857de2/graph Analysis, implementation and benchmarks by Claude Opus 5. --- src/TwigComponent/src/Twig/TwigPreLexer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/TwigComponent/src/Twig/TwigPreLexer.php b/src/TwigComponent/src/Twig/TwigPreLexer.php index 6043f2699c7..caf83cd27cc 100644 --- a/src/TwigComponent/src/Twig/TwigPreLexer.php +++ b/src/TwigComponent/src/Twig/TwigPreLexer.php @@ -366,8 +366,10 @@ private function consumeAttributes(string $componentName): string */ private function consume(string $string): bool { - if (str_starts_with(substr($this->input, $this->position), $string)) { - $this->position += \strlen($string); + $length = \strlen($string); + + if ($this->position + $length <= $this->length && 0 === substr_compare($this->input, $string, $this->position, $length)) { + $this->position += $length; return true; }