From 3e751c4e8716d0943b507f56e7ab446decff3924 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Sat, 28 Feb 2026 15:03:30 +0900 Subject: [PATCH] Add direct algo fast path in matchChunk For the common case of a single fuzzy term with no nth transform, call the algo function directly from matchChunk, bypassing the MatchItem -> extendedMatch -> iter dispatch chain. This eliminates 3 function calls and the per-match []Offset heap allocation. --- src/pattern.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ src/result.go | 11 ++++++++--- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/pattern.go b/src/pattern.go index 688ef433..94201bd4 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -65,6 +65,8 @@ type Pattern struct { cache *ChunkCache denylist map[int32]struct{} startIndex int32 + directAlgo algo.Algo + directTerm *term } var _splitRegex *regexp.Regexp @@ -151,6 +153,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo procFun: make(map[termType]algo.Algo)} ptr.cacheKey = ptr.buildCacheKey() + ptr.directAlgo, ptr.directTerm = ptr.buildDirectAlgo(fuzzyAlgo) ptr.procFun[termFuzzy] = fuzzyAlgo ptr.procFun[termEqual] = algo.EqualMatch ptr.procFun[termExact] = algo.ExactMatchNaive @@ -274,6 +277,22 @@ func (p *Pattern) buildCacheKey() string { return strings.Join(cacheableTerms, "\t") } +// buildDirectAlgo returns the algo function and term for the direct fast path +// in matchChunk. Returns (nil, nil) if the pattern is not suitable. +// Requirements: extended mode, single term set with single non-inverse fuzzy term, no nth. +func (p *Pattern) buildDirectAlgo(fuzzyAlgo algo.Algo) (algo.Algo, *term) { + if !p.extended || len(p.nth) > 0 { + return nil, nil + } + if len(p.termSets) == 1 && len(p.termSets[0]) == 1 { + t := &p.termSets[0][0] + if !t.inv && t.typ == termFuzzy { + return fuzzyAlgo, t + } + } + return nil, nil +} + // CacheKey is used to build string to be used as the key of result cache func (p *Pattern) CacheKey() string { return p.cacheKey @@ -312,6 +331,35 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Re } } + // Fast path: single fuzzy term, no nth, no denylist. + // Calls the algo function directly, bypassing MatchItem/extendedMatch/iter + // and avoiding per-match []Offset heap allocation. + if p.directAlgo != nil && len(p.denylist) == 0 { + t := p.directTerm + if space == nil { + for idx := startIdx; idx < chunk.count; idx++ { + res, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward, + &chunk.items[idx].text, t.text, p.withPos, slab) + if res.Start >= 0 { + matches = append(matches, buildResultFromBounds( + &chunk.items[idx], res.Score, + int(res.Start), int(res.End), int(res.End), true)) + } + } + } else { + for _, result := range space { + res, _ := p.directAlgo(t.caseSensitive, t.normalize, p.forward, + &result.item.text, t.text, p.withPos, slab) + if res.Start >= 0 { + matches = append(matches, buildResultFromBounds( + result.item, res.Score, + int(res.Start), int(res.End), int(res.End), true)) + } + } + } + return matches + } + if len(p.denylist) == 0 { // Huge code duplication for minimizing unnecessary map lookups if space == nil { diff --git a/src/result.go b/src/result.go index bfc5e9d3..957d5403 100644 --- a/src/result.go +++ b/src/result.go @@ -33,8 +33,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result { sort.Sort(ByOrder(offsets)) } - result := Result{item: item} - numChars := item.text.Length() minBegin := math.MaxUint16 minEnd := math.MaxUint16 maxEnd := 0 @@ -49,6 +47,14 @@ func buildResult(item *Item, offsets []Offset, score int) Result { } } + return buildResultFromBounds(item, score, minBegin, minEnd, maxEnd, validOffsetFound) +} + +// buildResultFromBounds builds a Result from pre-computed offset bounds. +func buildResultFromBounds(item *Item, score int, minBegin, minEnd, maxEnd int, validOffsetFound bool) Result { + result := Result{item: item} + numChars := item.text.Length() + for idx, criterion := range sortCriteria { val := uint16(math.MaxUint16) switch criterion { @@ -75,7 +81,6 @@ func buildResult(item *Item, offsets []Offset, score int) Result { val = item.TrimLength() case byPathname: if validOffsetFound { - // lastDelim := strings.LastIndexByte(item.text.ToString(), '/') lastDelim := -1 s := item.text.ToString() for i := len(s) - 1; i >= 0; i-- {