Uh oh!
There was an error while loading. Please reload this page.
perf: ASCII fast path for find_str_nocase, verified against the collator - #723
Merged
Conversation
After 3.8.9 the compiled runtime is dominated by case-insensitive string
matching. Profiling parse-en-us:
find_str_nocase 32.3% inclusive
Arun::match_list 28.6%
icu::Collator::compare 13.2%
icu::UnicodeString::fromUTF8 10.3%
icu::RuleBasedCollator::doCompare 9.6%
A third of the run is matching a node's name against a rule element's
match/fail/except list, through an ICU collator at PRIMARY strength.
When both sides are printable ASCII, PRIMARY-strength equality is exactly
byte-wise case-insensitive equality, so ICU can be skipped. That claim is
verified rather than assumed. A test program built against the same ICU
compared the collator with a byte-wise compare over:
all printable-ASCII character pairs (0x20-0x7E) 9,025 pairs, 0 mismatches
word/punctuation/spacing variants 1,521 pairs, 0 mismatches
control characters (0x01-0x1F) 124 pairs, 78 MISMATCHES
Control characters are primary-IGNORABLE: the collator considers "a\x01"
equal to "a". They are therefore excluded from the fast path, as are bytes
>= 0x80, which need real collation because PRIMARY strength also folds
accents ("cafe" matches an accented "cafe"). Either sends that one
comparison to ICU, so no matching behaviour changes.
The needle's UTF-16 form is now built lazily too, so a lookup whose
candidates are all plain ASCII -- overwhelmingly the common case -- does no
transcoding at all.
Interleaved A/B, 8 runs per arm, swapping nlp.exe and run.dll:
ICU collator (3.8.9) min 7.78s p25 8.10s median 8.75s
ASCII fast path min 7.02s p25 7.49s median 8.09s 1.08x - 1.11x
RuleBasedCollator::doCompare, ::compare and CollationFastLatin::compareUTF16
all drop out of the profile entirely; u_strFromUTF8WithSub halves.
All 18 analyzer output files byte-identical -- the strongest check
available here, since this function decides what the matcher matches.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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.
Where the time is now
With the I/O and stack-frame work done in 3.8.7/3.8.9, a fresh profile of released 3.8.9 shows the compiled runtime is dominated by case-insensitive string matching:
For comparison,
ntdll!ZwWriteFile— 25.5% back in 3.8.4 — is now 2.2%, and_chkstkis gone. A third of the run is matching a node's name against a rule element's match/fail/except list through an ICU collator at PRIMARY strength.The change
When both sides are printable ASCII, PRIMARY-strength equality is exactly byte-wise case-insensitive equality, so ICU can be skipped.
That claim is verified, not assumed. A test program built against the same ICU compared the collator against a byte-wise compare:
That last row is why the guard is "printable ASCII" and not "ASCII". Control characters are primary-ignorable — the collator considers
"a\x01"equal to"a". Bytes ≥ 0x80 are excluded too, since PRIMARY strength also folds accents (so"cafe"matches an accented"café", which may well be deliberate in an NLP engine). Either case sends that one comparison to ICU, so no matching behaviour changes.The needle's UTF-16 form is also built lazily now, so a lookup whose candidates are all plain ASCII — overwhelmingly the common case — does no transcoding at all.
Results
Interleaved A/B, 8 runs per arm, swapping both
nlp.exeandrun.dll:1.08x – 1.11x. Smaller than the 32% inclusive figure might suggest, because much of that is
match_list's own work rather than ICU. The mechanism did engage:RuleBasedCollator::doCompare,::compareandCollationFastLatin::compareUTF16all drop out of the profile entirely, andu_strFromUTF8WithSubhalves.Correctness
All 18 analyzer output files byte-identical. That's the strongest check available here — this function decides what the matcher matches, so any semantic drift would show up as different parse output.
🤖 Generated with Claude Code