Skip to content

Commit 29d42cb

Browse files
committed
perf: MergeFieldTermLocations fast-path + Reset nil-guard
Two micro-optimizations on per-scored-candidate hot paths: - MergeFieldTermLocations (search/util.go): return early when no constituent match carries any FieldTermLocations (n == len(dest)). This is the common case with no highlighting / location tracking, and skips the second iteration and its per-match merge calls entirely. - DocumentMatch.Reset (search/search.go): guard clear(scoreBreakdown) with a nil check. clear(nil) still dispatches through the Go map runtime (~1ns), and ScoreBreakdown is nil on the common path (no KNN, no score-breakdown retrieval). Benchmark (search package, added here), Apple M4 Pro, count=12 via benchstat: MergeFieldTermLocations (no locations) 5.28ns -> 2.68ns -49% (p=0.000) DocumentMatch.Reset (nil ScoreBreakdown) 6.62ns -> 5.76ns -13% (p=0.000) Absolute per-call costs are small, but both run per scored candidate (many millions of calls per query), so they trim steady per-candidate overhead. No allocation change.
1 parent 8fd0ead commit 29d42cb

3 files changed

Lines changed: 40 additions & 2 deletions

File tree

search/merge_reset_bench_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package search
2+
3+
import "testing"
4+
5+
// BenchmarkMergeFieldTermLocationsNoLocs: common no-highlighting case — several
6+
// constituent matches, none carrying field term locations. Exercises the
7+
// fast-path early return.
8+
func BenchmarkMergeFieldTermLocationsNoLocs(b *testing.B) {
9+
matches := make([]*DocumentMatch, 5)
10+
for i := range matches {
11+
matches[i] = &DocumentMatch{}
12+
}
13+
b.ResetTimer()
14+
b.ReportAllocs()
15+
var dest []FieldTermLocation
16+
for n := 0; n < b.N; n++ {
17+
dest = MergeFieldTermLocations(nil, matches)
18+
}
19+
_ = dest
20+
}
21+
22+
// BenchmarkDocumentMatchResetNilScoreBreakdown: Reset() on the common path where
23+
// ScoreBreakdown is nil. Exercises the clear(scoreBreakdown) nil-guard.
24+
func BenchmarkDocumentMatchResetNilScoreBreakdown(b *testing.B) {
25+
dm := &DocumentMatch{}
26+
b.ResetTimer()
27+
b.ReportAllocs()
28+
for n := 0; n < b.N; n++ {
29+
dm.Reset()
30+
}
31+
}

search/search.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,12 @@ func (dm *DocumentMatch) Reset() *DocumentMatch {
235235
}
236236
// remember the score breakdown map
237237
scoreBreakdown := dm.ScoreBreakdown
238-
// clear out the score breakdown map
239-
clear(scoreBreakdown)
238+
// clear out the score breakdown map; nil-guard because clear(nil) still
239+
// dispatches through the map runtime (~1ns), and ScoreBreakdown is nil on
240+
// the common path (no KNN, no score-breakdown retrieval)
241+
if scoreBreakdown != nil {
242+
clear(scoreBreakdown)
243+
}
240244
// remember the Descendants backing array
241245
descendants := dm.Descendants
242246
for i := range descendants { // recycle each IndexInternalID

search/util.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ func MergeFieldTermLocations(dest []FieldTermLocation, matches []*DocumentMatch)
5454
n += len(dm.FieldTermLocations)
5555
}
5656
}
57+
if n == len(dest) {
58+
return dest // fast path: no constituent has field term locations to merge
59+
}
5760
if cap(dest) < n {
5861
dest = append(make([]FieldTermLocation, 0, n), dest...)
5962
}

0 commit comments

Comments
 (0)