Skip to content

Commit 148ceff

Browse files
JanTvrdikondrejmirtes
authored andcommitted
Lexer: extract the matches with PREG_PATTERN_ORDER
PREG_SET_ORDER allocates one array per match. Now that the token patterns have no capturing groups those arrays hold two entries each -- the whole match and the MARK -- and tokenize() reads both out and drops the array again, so lexing pays one array allocation per token for nothing. PREG_PATTERN_ORDER collects the whole PHPDoc into two arrays instead: $matches[0] holds the values and $matches['MARK'] the token types. Two array headers per PHPDoc rather than one per token. Measured over 37150 unique docblocks (8.38 MiB, 2.36M tokens) collected from 20 OSS libraries, one full vendor tree, phpstan-src and this library: lexing parse (lex + PhpDocParser) PHP 8.1 -42% PHP 8.4 -40% PHP 8.5 -40% -19% tokenize() still returns list<array{string, int, int}>, and both the tokens and the ASTs printed from them are byte-identical to before over the whole corpus.
1 parent e59b0c8 commit 148ceff

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

src/Lexer/Lexer.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use PHPStan\PhpDocParser\ParserConfig;
66
use function implode;
77
use function preg_match_all;
8-
use const PREG_SET_ORDER;
98

109
/**
1110
* Implementation based on Nette Tokenizer (New BSD License; https://github.com/nette/tokenizer)
@@ -116,13 +115,24 @@ public function tokenize(string $s): array
116115
$this->regexp = $this->generateRegexp();
117116
}
118117

119-
preg_match_all($this->regexp, $s, $matches, PREG_SET_ORDER);
118+
// PREG_PATTERN_ORDER, not PREG_SET_ORDER: it collects the whole PHPDoc
119+
// into two arrays instead of allocating one array per token. This only
120+
// pays off while the token patterns have no capturing groups, because
121+
// every group would get an array of its own, one entry per token.
122+
preg_match_all($this->regexp, $s, $matches);
123+
124+
$values = $matches[0];
125+
if ($values === []) {
126+
return [['', self::TOKEN_END, 1]];
127+
}
128+
129+
$marks = $matches['MARK'];
120130

121131
$tokens = [];
122132
$line = 1;
123-
foreach ($matches as $match) {
124-
$type = (int) $match['MARK'];
125-
$tokens[] = [$match[0], $type, $line];
133+
foreach ($values as $i => $value) {
134+
$type = (int) $marks[$i];
135+
$tokens[] = [$value, $type, $line];
126136
if ($type !== self::TOKEN_PHPDOC_EOL) {
127137
continue;
128138
}
@@ -137,6 +147,7 @@ public function tokenize(string $s): array
137147

138148
private function generateRegexp(): string
139149
{
150+
// every group in here must be non-capturing, see tokenize()
140151
$patterns = [
141152
self::TOKEN_HORIZONTAL_WS => '[\\x09\\x20]++',
142153

0 commit comments

Comments
 (0)