mirror of
https://github.com/peco/peco.git
synced 2026-09-10 07:16:29 -04:00
144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
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 (CaseInsensitiveIndexFunc closure, match slices, etc).
|
|
func BenchmarkFuzzyFilter(b *testing.B) {
|
|
lines := make([]line.Line, 200)
|
|
for i := range lines {
|
|
lines[i] = line.NewRaw(uint64(i), "this is a reasonably long line for benchmarking the fuzzy filter path", false, false)
|
|
}
|
|
|
|
f := NewFuzzy(false)
|
|
query := "trfp" // matches scattered chars across the line
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for b.Loop() {
|
|
ctx, cancel := context.WithTimeout(f.NewContext(context.Background(), query), 10*time.Second)
|
|
ch := make(chan line.Line, len(lines))
|
|
_ = f.Apply(ctx, lines, pipeline.ChanOutput(ch))
|
|
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)
|
|
}
|
|
}
|
|
|
|
// BenchmarkRegexpFilter benchmarks the regexp filter with overlapping matches
|
|
// to exercise the dedup/merge path and matches slice allocation.
|
|
func BenchmarkRegexpFilter(b *testing.B) {
|
|
lines := make([]line.Line, 200)
|
|
for i := range lines {
|
|
lines[i] = line.NewRaw(uint64(i), "the quick brown fox jumps over the lazy brown dog", false, false)
|
|
}
|
|
|
|
f := NewIgnoreCase()
|
|
query := "brown the"
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for b.Loop() {
|
|
ctx, cancel := context.WithTimeout(f.NewContext(context.Background(), query), 10*time.Second)
|
|
ch := make(chan line.Line, len(lines))
|
|
_ = f.Apply(ctx, lines, pipeline.ChanOutput(ch))
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
// BenchmarkRegexpFilterMultiCycle simulates multiple keystroke filter cycles.
|
|
// On each cycle, the filter produces Matched objects which are collected in a
|
|
// MemoryBuffer, then Reset() is called to release them before the next cycle.
|
|
// This demonstrates pool reuse across keystrokes.
|
|
func BenchmarkRegexpFilterMultiCycle(b *testing.B) {
|
|
const numLines = 10_000
|
|
lines := make([]line.Line, numLines)
|
|
for i := range lines {
|
|
lines[i] = line.NewRaw(uint64(i), "the quick brown fox jumps over the lazy dog", false, false)
|
|
}
|
|
|
|
f := NewIgnoreCase()
|
|
query := "fox"
|
|
|
|
b.ReportAllocs()
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
// Simulate 3 keystroke cycles
|
|
for range 3 {
|
|
ctx := f.NewContext(context.Background(), query)
|
|
ch := make(chan line.Line, numLines)
|
|
_ = f.Apply(ctx, lines, pipeline.ChanOutput(ch))
|
|
close(ch)
|
|
|
|
// Drain and release (simulating MemoryBuffer.Reset)
|
|
for l := range ch {
|
|
if m, ok := l.(*line.Matched); ok {
|
|
line.ReleaseMatched(m)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkRegexpFilterOverlapping benchmarks the regexp filter with a query
|
|
// that produces overlapping match ranges, exercising the mergeMatches path.
|
|
// Each line contains repeating "aabb" patterns; the query terms "aab" and "abb"
|
|
// produce match ranges that overlap (e.g. [0,3] and [1,4]), forcing mergeMatches
|
|
// to be called on every line. With 10k lines the aggregate allocation difference
|
|
// from in-place vs make([]int,2) becomes measurable.
|
|
func BenchmarkRegexpFilterOverlapping(b *testing.B) {
|
|
// Build a line with many "aabb" repeats so that "aab" and "abb" each match
|
|
// many times with overlapping ranges between the two terms.
|
|
base := strings.Repeat("aabb", 20) // 80 chars
|
|
lines := make([]line.Line, 10_000)
|
|
for i := range lines {
|
|
lines[i] = line.NewRaw(uint64(i), base, false, false)
|
|
}
|
|
|
|
f := NewIgnoreCase()
|
|
// "aab" and "abb" share the middle "ab" in each "aabb" group, so their
|
|
// match ranges overlap after sorting by start position.
|
|
query := "aab abb"
|
|
|
|
b.ResetTimer()
|
|
b.ReportAllocs()
|
|
for b.Loop() {
|
|
ctx, cancel := context.WithTimeout(f.NewContext(context.Background(), query), 10*time.Second)
|
|
ch := make(chan line.Line, len(lines))
|
|
_ = f.Apply(ctx, lines, pipeline.ChanOutput(ch))
|
|
cancel()
|
|
}
|
|
}
|