Merge pull request #783 from peco/fix-closure-alloc

fix: eliminate closure allocation in fuzzy filter
This commit is contained in:
lestrrat 2026-02-21 22:26:45 +09:00 committed by GitHub
commit a91172b458
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 44 additions and 4 deletions

View file

@ -2,15 +2,17 @@ package filter
import (
"context"
"strings"
"testing"
"time"
"github.com/peco/peco/internal/util"
"github.com/peco/peco/line"
"github.com/peco/peco/pipeline"
)
// BenchmarkFuzzyFilter benchmarks the fuzzy filter to measure allocations
// in the hot path.
// in the hot path (CaseInsensitiveIndexFunc closure, match slices, etc).
func BenchmarkFuzzyFilter(b *testing.B) {
lines := make([]line.Line, 200)
for i := range lines {
@ -18,7 +20,7 @@ func BenchmarkFuzzyFilter(b *testing.B) {
}
f := NewFuzzy(false)
query := "trfp"
query := "trfp" // matches scattered chars across the line
b.ResetTimer()
b.ReportAllocs()
@ -29,3 +31,28 @@ func BenchmarkFuzzyFilter(b *testing.B) {
cancel()
}
}
// BenchmarkCaseInsensitiveIndexClosure measures the old closure-based approach.
func BenchmarkCaseInsensitiveIndexClosure(b *testing.B) {
txt := "this is a reasonably long line for benchmarking"
r := 'r'
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
fn := util.CaseInsensitiveIndexFunc(r)
strings.IndexFunc(txt, fn)
}
}
// BenchmarkCaseInsensitiveIndexDirect measures the new direct approach.
func BenchmarkCaseInsensitiveIndexDirect(b *testing.B) {
txt := "this is a reasonably long line for benchmarking"
r := 'r'
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
util.CaseInsensitiveIndex(txt, r)
}
}

View file

@ -108,7 +108,7 @@ LINE:
if hasUpper {
idx = strings.IndexRune(remaining, firstRune)
} else {
idx = strings.IndexFunc(remaining, util.CaseInsensitiveIndexFunc(firstRune))
idx = util.CaseInsensitiveIndex(remaining, firstRune)
}
if idx == -1 {
break
@ -157,7 +157,7 @@ LINE:
if hasUpper {
idx = strings.IndexRune(candidateTxt, r)
} else {
idx = strings.IndexFunc(candidateTxt, util.CaseInsensitiveIndexFunc(r))
idx = util.CaseInsensitiveIndex(candidateTxt, r)
}
if idx == -1 {
continue OUTER

View file

@ -18,6 +18,19 @@ func CaseInsensitiveIndexFunc(r rune) func(rune) bool {
}
}
// CaseInsensitiveIndex returns the byte index of the first rune in s that
// is case-insensitively equal to r. Returns -1 if not found. This avoids
// the closure allocation of CaseInsensitiveIndexFunc + strings.IndexFunc.
func CaseInsensitiveIndex(s string, r rune) int {
upper := unicode.ToUpper(r)
for i, c := range s {
if unicode.ToUpper(c) == upper {
return i
}
}
return -1
}
// ContainsUpper reports whether the string contains any uppercase letter.
func ContainsUpper(query string) bool {
for _, c := range query {