Skip to content

Commit 13cfb15

Browse files
agnivadeTocarIP
authored andcommitted
strings: optimize ToUpper
Handling the ASCII case inline and call unicode.ToUpper only for non-ascii cases. Gives good improvements for the ascii case and minor perf degrade for non-ascii case name old time/op new time/op delta ToUpper/#00 11.7ns ± 8% 8.0ns ± 1% -31.95% (p=0.008 n=5+5) ToUpper/ONLYUPPER 45.6ns ± 5% 19.9ns ± 1% -56.40% (p=0.008 n=5+5) ToUpper/abc 77.4ns ± 1% 57.0ns ± 1% -26.32% (p=0.008 n=5+5) ToUpper/AbC123 92.1ns ± 4% 67.7ns ± 2% -26.57% (p=0.008 n=5+5) ToUpper/azAZ09_ 105ns ± 6% 67ns ± 2% -36.26% (p=0.000 n=5+4) ToUpper/longStrinGwitHmixofsmaLLandcAps 255ns ± 1% 140ns ± 1% -45.01% (p=0.029 n=4+4) ToUpper/longɐstringɐwithɐnonasciiⱯchars 440ns ± 1% 447ns ± 0% +1.49% (p=0.016 n=5+4) ToUpper/ɐɐɐɐɐ 370ns ± 4% 366ns ± 1% ~ (p=0.667 n=5+5) name old alloc/op new alloc/op delta ToUpper/#00 0.00B 0.00B ~ (all equal) ToUpper/ONLYUPPER 0.00B 0.00B ~ (all equal) ToUpper/abc 16.0B ± 0% 6.0B ± 0% -62.50% (p=0.008 n=5+5) ToUpper/AbC123 16.0B ± 0% 16.0B ± 0% ~ (all equal) ToUpper/azAZ09_ 24.0B ± 0% 16.0B ± 0% -33.33% (p=0.008 n=5+5) ToUpper/longStrinGwitHmixofsmaLLandcAps 80.0B ± 0% 64.0B ± 0% -20.00% (p=0.008 n=5+5) ToUpper/longɐstringɐwithɐnonasciiⱯchars 96.0B ± 0% 96.0B ± 0% ~ (all equal) ToUpper/ɐɐɐɐɐ 64.0B ± 0% 64.0B ± 0% ~ (all equal) Ran on a machine with Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz Updates #17859 Change-Id: I0735ac4a4a36e8a8f6cc06f2c16b871f12b4abf9 Reviewed-on: https://go-review.googlesource.com/68370 Reviewed-by: Joe Tsai <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]> Run-TryBot: Joe Tsai <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent f10d99f commit 13cfb15

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/strings/strings.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,33 @@ func Repeat(s string, count int) string {
542542
}
543543

544544
// ToUpper returns a copy of the string s with all Unicode letters mapped to their upper case.
545-
func ToUpper(s string) string { return Map(unicode.ToUpper, s) }
545+
func ToUpper(s string) string {
546+
isASCII, hasLower := true, false
547+
for i := 0; i < len(s); i++ {
548+
c := s[i]
549+
if c >= utf8.RuneSelf {
550+
isASCII = false
551+
break
552+
}
553+
hasLower = hasLower || (c >= 'a' && c <= 'z')
554+
}
555+
556+
if isASCII { // optimize for ASCII-only strings.
557+
if !hasLower {
558+
return s
559+
}
560+
b := make([]byte, len(s))
561+
for i := 0; i < len(s); i++ {
562+
c := s[i]
563+
if c >= 'a' && c <= 'z' {
564+
c -= 'a' - 'A'
565+
}
566+
b[i] = c
567+
}
568+
return string(b)
569+
}
570+
return Map(unicode.ToUpper, s)
571+
}
546572

547573
// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case.
548574
func ToLower(s string) string { return Map(unicode.ToLower, s) }

src/strings/strings_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,9 +518,12 @@ func runStringTests(t *testing.T, f func(string) string, funcName string, testCa
518518

519519
var upperTests = []StringTest{
520520
{"", ""},
521+
{"ONLYUPPER", "ONLYUPPER"},
521522
{"abc", "ABC"},
522523
{"AbC123", "ABC123"},
523524
{"azAZ09_", "AZAZ09_"},
525+
{"longStrinGwitHmixofsmaLLandcAps", "LONGSTRINGWITHMIXOFSMALLANDCAPS"},
526+
{"long\u0250string\u0250with\u0250nonascii\u2C6Fchars", "LONG\u2C6FSTRING\u2C6FWITH\u2C6FNONASCII\u2C6FCHARS"},
524527
{"\u0250\u0250\u0250\u0250\u0250", "\u2C6F\u2C6F\u2C6F\u2C6F\u2C6F"}, // grows one byte per char
525528
}
526529

@@ -648,6 +651,19 @@ func TestToUpper(t *testing.T) { runStringTests(t, ToUpper, "ToUpper", upperTest
648651

649652
func TestToLower(t *testing.T) { runStringTests(t, ToLower, "ToLower", lowerTests) }
650653

654+
func BenchmarkToUpper(b *testing.B) {
655+
for _, tc := range upperTests {
656+
b.Run(tc.in, func(b *testing.B) {
657+
for i := 0; i < b.N; i++ {
658+
actual := ToUpper(tc.in)
659+
if actual != tc.out {
660+
b.Errorf("ToUpper(%q) = %q; want %q", tc.in, actual, tc.out)
661+
}
662+
}
663+
})
664+
}
665+
}
666+
651667
func BenchmarkMapNoChanges(b *testing.B) {
652668
identity := func(r rune) rune {
653669
return r

0 commit comments

Comments
 (0)