From 49e32f3f8ae509ae818a6501cd9de51287eb586c Mon Sep 17 00:00:00 2001 From: greatroar <61184462+greatroar@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:13:39 +0200 Subject: [PATCH] ui/termstatus: Optimize Truncate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit x/text/width.LookupRune has to re-encode its argument as UTF-8, while LookupString operates on the UTF-8 directly. The uint casts get rid of a bounds check. Benchmark results, with b.ResetTimer introduced first: name old time/op new time/op delta TruncateASCII-8 69.7ns ± 1% 55.2ns ± 1% -20.90% (p=0.000 n=20+18) TruncateUnicode-8 350ns ± 1% 171ns ± 1% -51.05% (p=0.000 n=20+19) --- internal/ui/termstatus/status.go | 27 ++++++++++++++++++--------- internal/ui/termstatus/status_test.go | 8 ++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/internal/ui/termstatus/status.go b/internal/ui/termstatus/status.go index fdc7e14f6..26fe8184b 100644 --- a/internal/ui/termstatus/status.go +++ b/internal/ui/termstatus/status.go @@ -302,26 +302,35 @@ func Truncate(s string, w int) string { return s } - for i, r := range s { + for i := uint(0); i < uint(len(s)); { + utfsize := uint(1) // UTF-8 encoding size of first rune in s. w-- - if r > unicode.MaxASCII && wideRune(r) { - w-- + + if s[i] > unicode.MaxASCII { + var wide bool + if wide, utfsize = wideRune(s[i:]); wide { + w-- + } } if w < 0 { return s[:i] } + i += utfsize } return s } -// Guess whether r would occupy two terminal cells instead of one. -// This cannot be determined exactly without knowing the terminal font, -// so we treat all ambigous runes as full-width, i.e., two cells. -func wideRune(r rune) bool { - kind := width.LookupRune(r).Kind() - return kind != width.Neutral && kind != width.EastAsianNarrow +// Guess whether the first rune in s would occupy two terminal cells +// instead of one. This cannot be determined exactly without knowing +// the terminal font, so we treat all ambigous runes as full-width, +// i.e., two cells. +func wideRune(s string) (wide bool, utfsize uint) { + prop, size := width.LookupString(s) + kind := prop.Kind() + wide = kind != width.Neutral && kind != width.EastAsianNarrow + return wide, uint(size) } // SetStatus updates the status lines. diff --git a/internal/ui/termstatus/status_test.go b/internal/ui/termstatus/status_test.go index ce18f42e6..696684502 100644 --- a/internal/ui/termstatus/status_test.go +++ b/internal/ui/termstatus/status_test.go @@ -49,11 +49,15 @@ func BenchmarkTruncateASCII(b *testing.B) { func BenchmarkTruncateUnicode(b *testing.B) { s := "Hello World or Καλημέρα κόσμε or こんにちは 世界" w := 0 - for _, r := range s { + for i := 0; i < len(s); { w++ - if wideRune(r) { + wide, utfsize := wideRune(s[i:]) + if wide { w++ } + i += int(utfsize) } + b.ResetTimer() + benchmarkTruncate(b, s, w-1) }