From d4088775cd73c89f1cf7690f9fee350d4f3b8bf0 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 4 Oct 2025 16:48:19 -0700 Subject: [PATCH 1/2] Speed up levenshteinWithMax by reusing buffers --- internal/core/core.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/core/core.go b/internal/core/core.go index 3bd84b8075..e3b6b2fd20 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -7,6 +7,7 @@ import ( "slices" "sort" "strings" + "sync" "unicode" "unicode/utf8" @@ -502,9 +503,28 @@ func GetSpellingSuggestion[T any](name string, candidates []T, getName func(T) s return bestCandidate } +type levenshteinBuffers struct { + previous []float64 + current []float64 +} + +var levenshteinBuffersPool = sync.Pool{ + New: func() any { + return &levenshteinBuffers{} + }, +} + func levenshteinWithMax(s1 []rune, s2 []rune, maxValue float64) float64 { - previous := make([]float64, len(s2)+1) - current := make([]float64, len(s2)+1) + buffers := levenshteinBuffersPool.Get().(*levenshteinBuffers) + defer levenshteinBuffersPool.Put(buffers) + + bufferSize := len(s2) + 1 + buffers.previous = slices.Grow(buffers.previous[:0], bufferSize)[:bufferSize] + buffers.current = slices.Grow(buffers.current[:0], bufferSize)[:bufferSize] + + previous := buffers.previous + current := buffers.current + big := maxValue + 0.01 for i := range previous { previous[i] = float64(i) From 2b0da2dc30dc1945a7cebf60eedc44e88362c166 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sun, 12 Oct 2025 14:54:09 -0700 Subject: [PATCH 2/2] Lift buffers --- internal/core/core.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/internal/core/core.go b/internal/core/core.go index e3b6b2fd20..b9126f4e3c 100644 --- a/internal/core/core.go +++ b/internal/core/core.go @@ -477,6 +477,8 @@ func GetSpellingSuggestion[T any](name string, candidates []T, getName func(T) s maximumLengthDifference := max(2, int(float64(len(name))*0.34)) bestDistance := math.Floor(float64(len(name))*0.4) + 1 // If the best result is worse than this, don't bother. runeName := []rune(name) + buffers := levenshteinBuffersPool.Get().(*levenshteinBuffers) + defer levenshteinBuffersPool.Put(buffers) var bestCandidate T for _, candidate := range candidates { candidateName := getName(candidate) @@ -491,7 +493,7 @@ func GetSpellingSuggestion[T any](name string, candidates []T, getName func(T) s if len(candidateName) < 3 && !strings.EqualFold(candidateName, name) { continue } - distance := levenshteinWithMax(runeName, []rune(candidateName), bestDistance-0.1) + distance := levenshteinWithMax(buffers, runeName, []rune(candidateName), bestDistance-0.1) if distance < 0 { continue } @@ -514,10 +516,7 @@ var levenshteinBuffersPool = sync.Pool{ }, } -func levenshteinWithMax(s1 []rune, s2 []rune, maxValue float64) float64 { - buffers := levenshteinBuffersPool.Get().(*levenshteinBuffers) - defer levenshteinBuffersPool.Put(buffers) - +func levenshteinWithMax(buffers *levenshteinBuffers, s1 []rune, s2 []rune, maxValue float64) float64 { bufferSize := len(s2) + 1 buffers.previous = slices.Grow(buffers.previous[:0], bufferSize)[:bufferSize] buffers.current = slices.Grow(buffers.current[:0], bufferSize)[:bufferSize]