Skip to content

Use faster strpbrk() over regex matching - #7004

Closed
staabm wants to merge 1 commit into
phpstan:2.3.xfrom
staabm:strpbrk
Closed

staabm wants to merge 1 commit into
phpstan:2.3.xfrom
staabm:strpbrk

Conversation

@staabm

@staabm staabm commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

inspired by PHP-CS-Fixer/PHP-CS-Fixer#9924

benchmark strpbrk() with trim/preg_match():

➜  phpstan-src git:(pr/6954) ✗ php tests/bench/micro/fnmatch-pattern.php        
PHP 8.5.12; PCRE JIT: 1; 200000 iterations, median of 5 rounds
Input                 regex ns/op strpbrk ns/op    speedup
no wildcard                  42.3         35.5      1.19x
early wildcard               32.6         30.9      1.05x
late wildcard                39.5         36.3      1.09x
bracket pattern              37.5         33.0      1.14x
long no wildcard            108.0         86.4      1.25x
long late wildcard          106.2         91.0      1.17x

➜  phpstan-src git:(2.3.x) ✗ php tests/bench/micro/trim-digits.php   
PHP 8.5.12; PCRE JIT: 1; 200000 iterations, median of 5 rounds
Input                 regex ns/op strpbrk ns/op    speedup
empty                        32.8         26.7      1.23x
whitespace                   34.8         37.5      0.93x
no digit                     34.9         29.2      1.20x
early digit                  34.2         34.1      1.00x
late digit                   36.1         33.8      1.07x
trim range                   34.2         31.6      1.08x
long no digit                82.6         76.9      1.07x
long late digit              84.4         84.1      1.00x

@staabm
staabm requested a review from VincentLanglet November 8, 2026 08:52
@staabm

staabm commented Nov 8, 2026

Copy link
Copy Markdown
Contributor Author

//cc @SanderMuller

@SanderMuller SanderMuller left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both replacements give the same result as the old regexes. I compared them on 201,025 strings: every byte value alone, doubled and next to a letter, and random strings of up to 10 characters. The random strings used digits, the 4 wildcard characters, \xB2, an Arabic-Indic digit and NUL. There was no difference, also with the UTF-8 ctype locale that the CLI sets.

On the PR head, FileExcluderTest, the trim tests in NodeScopeResolverTest, phpcs and make phpstan pass. The 3 red integration jobs are also red on #7007. Mutation Testing (8.4) did not run, because the download of infection got a 504 Gateway Time-out.

The run-time effect is very small. Self-analysis reaches the changed checks 1,591 times. With the largest difference in your tables (about 22 ns), that is less than 0.1 ms per run.

A separate problem that is older than this PR: trim() accepts a range like '/..:', which includes all digits without a digit character. So for a numeric-string $s, PHPStan types trim($s, '/..:') as numeric-string, but trim('12', '/..:') is ''. The old regex has the same gap. I can open an issue for it.

@staabm

staabm commented Nov 9, 2026

Copy link
Copy Markdown
Contributor Author

I can open an issue for it.

yes, please open a issue

@SanderMuller

Copy link
Copy Markdown
Contributor

Opened phpstan/phpstan#15683. I also found that chop() on a constant string removes characters from the start, so I opened phpstan/phpstan#15684 for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants