Skip the normalization map for runes that cannot normalize

normalizeRune guarded with 0x00C0..0xFF61, which does not exclude Hangul,
CJK or Cyrillic, so every rune of those scripts hashed into the map only
to miss. Every key of the map folds to ASCII, so the bitmap added for the
rune prefilter rejects them without a lookup.

- Non-ASCII queries 1.21x where every line is CJK, 1.05x on mostly-ASCII
  paths behind a Hangul prefix
- ASCII queries unchanged, the prefilter already skips Phase 2 for them
- Normalization share of query time for a non-ASCII query: 17.2% -> 0%
This commit is contained in:
Junegunn Choi
2026-08-08 19:10:37 +09:00
parent a650900eda
commit 465837f3ad
2 changed files with 32 additions and 1 deletions
+3 -1
View File
@@ -303,7 +303,9 @@ func bonusAt(input *util.Chars, idx int) int16 {
}
func normalizeRune(r rune) rune {
if r < 0x00C0 || r > 0xFF61 {
// Every key of the map folds to ASCII, so a rune the bitmap rejects cannot
// be in it. TestNormalizedKeysAreFlagged pins that.
if !util.MayFoldToAscii(r) {
return r
}
+29
View File
@@ -226,6 +226,35 @@ func TestRunePrefilterEquivalenceV1(t *testing.T) {
disableRunePrefilter = false
}
// normalizeRune skips the map when util.MayFoldToAscii rejects the rune. That
// is only sound if every key of the map is flagged, since a flagged-false rune
// is returned unchanged.
func TestNormalizedKeysAreFlagged(t *testing.T) {
for k := range normalized {
if !util.MayFoldToAscii(k) {
t.Errorf("normalized key U+%04X (%c) is not flagged by MayFoldToAscii", k, k)
}
}
}
// Guarding normalizeRune must not change what it returns, for any rune.
func TestNormalizeRuneUnchangedByGuard(t *testing.T) {
for r := rune(0); r <= unicode.MaxRune; r++ {
if r >= 0xD800 && r <= 0xDFFF {
continue
}
exp := r
if r >= 0x00C0 && r <= 0xFF61 {
if n := normalized[r]; n > 0 {
exp = n
}
}
if got := normalizeRune(r); got != exp {
t.Fatalf("normalizeRune(U+%04X) = U+%04X, expected U+%04X", r, got, exp)
}
}
}
// preparePattern mirrors what pattern.go guarantees the algo functions:
// lowercased when case-insensitive, normalized when normalize is on.
func preparePattern(pat string, caseSensitive, normalize bool) []rune {