Repository navigation
[RFC] ext/standard: Add prepared string matching and replacement - #25122
Open
ArtUkrainskiy wants to merge 1 commit into
Open
ArtUkrainskiy wants to merge 1 commit into
ArtUkrainskiy wants to merge 1 commit into
Conversation
ArtUkrainskiy
force-pushed
the
str-matcher-prototype
branch
from
November 2, 2026 19:09
5025a32 to
f212775
Compare
ArtUkrainskiy
force-pushed
the
str-matcher-prototype
branch
2 times, most recently
from
November 5, 2026 10:41
287aaab to
7c7550b
Compare
ArtUkrainskiy
marked this pull request as ready for review
November 6, 2026 12:59
TimWolla
reviewed
Nov 6, 2026
TimWolla
left a comment
Member
There was a problem hiding this comment.
This absolutely should be part of ext/standard. Making it a separate extension is needless (maintenance) overhead.
ArtUkrainskiy
force-pushed
the
str-matcher-prototype
branch
from
November 11, 2026 00:45
571da6b to
bb50862
Compare
Add final Str\Matcher and Str\Replacer classes that prepare a fixed dictionary of literal keys once and reuse it across subjects. Str\Matcher reports whether any key occurs, the matched keys, the number of occurrences and every occurrence with its byte offset. Str\Replacer applies leftmost-longest replacement with strtr() array semantics. Matcher keys must be strings. Both build a hybrid Aho-Corasick automaton: dense rows near the root, packed sparse transitions deeper, row offsets stored as state IDs. Replacement scans a reversed automaton. Objects compare equal when they hold the same key set or mapping, and are not cloneable or serializable; neither is MatchResult. Include 15 PHPT tests for API semantics, binary input, overlapping matches, the replacement oracle and object lifecycle.
ArtUkrainskiy
force-pushed
the
str-matcher-prototype
branch
from
November 12, 2026 13:34
bb50862 to
a3f22b7
Compare
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.
RFC: https://wiki.php.net/rfc/prepared_literal_string_matching
Discussion: https://news-web.php.net/php.internals/133028
Benchmark report: https://github.com/ArtUkrainskiy/php-src-bench/tree/main/reports/multi-pattern-search
Str\MatcherandStr\Replacertake a dictionary of literal keys once and search or replace all of them in one pass over the text.Change in time per call against the fastest existing method for the same input (
str_contains()/strpos()/substr_count()loop,strtr()or PCRE). Random keys. Each range covers all five calls, match positions and both compilers. Negative = faster.AMD Ryzen 9 5950X, GCC 15.2 and Clang 21.1, calls go through a pool of different texts. Not in the table:
matches()with a match in the first 64 bytes, where the existing methods stop early. There a prepared object gives −70…+49% with 32 keys or more.On real data:
_, 597 B manual pagesOn the same 4–16 KiB pages with all 3,345 names, one
matchAll()is 60–62% faster, but onereplace()is 32–40% slower thanstrtr(). On 1–2 KiB pages one call doesn't pay off.On short texts the gain needs a reused object: a long-running worker, or a loop over many texts. Building a Matcher costs 21–58 µs per KiB of keys with GCC: 117 µs and 134 KiB for the moderation list, 1,640 µs and 809 KiB for the 3,345 function names. With 1 or 4 keys the existing methods are 1.9–22× faster.
matchAll()returns every occurrence, overlapping ones included, asStr\MatchResultobjects with byte offsets.replace()followsstrtr()array semantics: leftmost match, longest key, no rescanning of replaced text. Matching is byte-wise, binary-safe and case-sensitive. Both classes use an Aho–Corasick automaton with dense rows near the root and packed sparse rows deeper.15 PHPT tests cover the API contract, binary input, overlapping matches, object lifecycle and 350 seeded cases against
strpos()andstrtr()oracles.Not measured: other CPUs, cold caches, the JIT, userland Aho–Corasick libraries.