From 1faf692919fd908a17067be2ea078fb6fe6a2e26 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 2 May 2023 16:12:54 -0700 Subject: [PATCH] perf: avoid allocs in unicodeFoldTransformer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is no need for temporary slices; the transformation can happen rune-by-rune. goos: darwin goarch: arm64 pkg: github.com/lithammer/fuzzysearch/fuzzy │ a │ b │ │ sec/op │ sec/op vs base │ Match-8 16.06n ± 3% 16.04n ± 3% ~ (p=0.447 n=10) MatchBigLate-8 1.008µ ± 0% 1.007µ ± 1% ~ (p=1.000 n=10) MatchBigEarly-8 12.56n ± 2% 12.58n ± 3% ~ (p=0.303 n=10) MatchFold-8 334.5n ± 1% 137.9n ± 2% -58.77% (p=0.000 n=10) MatchFoldBigLate-8 27.349µ ± 1% 7.088µ ± 1% -74.08% (p=0.000 n=10) MatchFoldBigEarly-8 26.390µ ± 2% 6.091µ ± 9% -76.92% (p=0.000 n=10) RankMatch-8 17.66n ± 1% 17.59n ± 0% -0.40% (p=0.034 n=10) RankMatchBigLate-8 1.010µ ± 1% 1.008µ ± 3% ~ (p=0.368 n=10) RankMatchBigEarly-8 1.236µ ± 0% 1.235µ ± 2% ~ (p=0.667 n=10) LevenshteinDistance-8 55.65n ± 3% 55.52n ± 3% ~ (p=0.054 n=10) LevenshteinDistanceBigLate-8 20.53µ ± 1% 20.47µ ± 3% -0.29% (p=0.042 n=10) LevenshteinDistanceBigEarly-8 20.57µ ± 3% 20.47µ ± 3% ~ (p=0.060 n=10) geomean 736.4n 540.1n -26.66% │ a │ b │ │ B/op │ B/op vs base │ Match-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ MatchBigLate-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ MatchBigEarly-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ MatchFold-8 720.0 ± 0% 512.0 ± 0% -28.89% (p=0.000 n=10) MatchFoldBigLate-8 36.711Ki ± 0% 8.750Ki ± 0% -76.17% (p=0.000 n=10) MatchFoldBigEarly-8 36.711Ki ± 0% 8.750Ki ± 0% -76.17% (p=0.000 n=10) RankMatch-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ RankMatchBigLate-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ RankMatchBigEarly-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ LevenshteinDistance-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ LevenshteinDistanceBigLate-8 6.375Ki ± 0% 6.375Ki ± 0% ~ (p=1.000 n=10) ¹ LevenshteinDistanceBigEarly-8 6.375Ki ± 0% 6.375Ki ± 0% ~ (p=1.000 n=10) ¹ geomean ² -23.46% ² ¹ all samples are equal ² summaries must be >0 to compute geomean │ a │ b │ │ allocs/op │ allocs/op vs base │ Match-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ MatchBigLate-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ MatchBigEarly-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ MatchFold-8 10.000 ± 0% 2.000 ± 0% -80.00% (p=0.000 n=10) MatchFoldBigLate-8 186.00 ± 0% 25.00 ± 0% -86.56% (p=0.000 n=10) MatchFoldBigEarly-8 186.00 ± 0% 25.00 ± 0% -86.56% (p=0.000 n=10) RankMatch-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ RankMatchBigLate-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ RankMatchBigEarly-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ LevenshteinDistance-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ LevenshteinDistanceBigLate-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ LevenshteinDistanceBigEarly-8 1.000 ± 0% 1.000 ± 0% ~ (p=1.000 n=10) ¹ geomean ² -37.41% ² ¹ all samples are equal ² summaries must be >0 to compute geomean --- fuzzy/fuzzy.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/fuzzy/fuzzy.go b/fuzzy/fuzzy.go index 7ae7091..98b06be 100644 --- a/fuzzy/fuzzy.go +++ b/fuzzy/fuzzy.go @@ -3,7 +3,6 @@ package fuzzy import ( - "bytes" "unicode" "unicode/utf8" @@ -251,18 +250,21 @@ func stringTransform(s string, t transform.Transformer) (transformed string) { type unicodeFoldTransformer struct{ transform.NopResetter } func (unicodeFoldTransformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { - runes := bytes.Runes(src) - var lowerRunes []rune - for _, r := range runes { - lowerRunes = append(lowerRunes, unicode.ToLower(r)) - } - - srcBytes := []byte(string(lowerRunes)) - n := copy(dst, srcBytes) - if n < len(srcBytes) { - err = transform.ErrShortDst + n := 0 + // Converting src to a string allocates. + // In theory, it need not; see https://go.dev/issue/27148. + // It is possible to write this loop using utf8.DecodeRune + // and thereby avoid allocations, but it is noticeably slower. + // So just let's wait for the compiler to get smarter. + for _, r := range string(src) { + r = unicode.ToLower(r) + x := utf8.RuneLen(r) + if x > len(dst[n:]) { + err = transform.ErrShortDst + break + } + n += utf8.EncodeRune(dst[n:], r) } - return n, n, err }