Skip to content

Commit 65fbf0e

Browse files
agnivadedsnet
authored andcommitted
strings: optimize ToLower
Handling the ASCII case inline and call unicode.ToLower 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 ToLower/#00 10.8ns ± 1% 9.0ns ± 1% -16.83% (p=0.008 n=5+5) ToLower/abc 23.3ns ± 4% 12.6ns ± 1% -46.01% (p=0.008 n=5+5) ToLower/AbC123 91.0ns ± 2% 70.4ns ± 0% -22.59% (p=0.008 n=5+5) ToLower/azAZ09_ 104ns ± 3% 75ns ± 1% -28.35% (p=0.008 n=5+5) ToLower/longStrinGwitHmixofsmaLLandcAps 254ns ± 4% 157ns ± 0% -38.19% (p=0.016 n=5+4) ToLower/LONGⱯSTRINGⱯWITHⱯNONASCIIⱯCHARS 446ns ± 1% 451ns ± 1% ~ (p=0.056 n=5+5) ToLower/ⱭⱭⱭⱭⱭ 345ns ± 1% 348ns ± 0% +0.93% (p=0.016 n=5+5) name old alloc/op new alloc/op delta ToLower/#00 0.00B 0.00B ~ (all equal) ToLower/abc 0.00B 0.00B ~ (all equal) ToLower/AbC123 16.0B ± 0% 16.0B ± 0% ~ (all equal) ToLower/azAZ09_ 24.0B ± 0% 16.0B ± 0% -33.33% (p=0.008 n=5+5) ToLower/longStrinGwitHmixofsmaLLandcAps 80.0B ± 0% 64.0B ± 0% -20.00% (p=0.008 n=5+5) ToLower/LONGⱯSTRINGⱯWITHⱯNONASCIIⱯCHARS 96.0B ± 0% 96.0B ± 0% ~ (all equal) ToLower/ⱭⱭⱭⱭⱭ 48.0B ± 0% 48.0B ± 0% ~ (all equal) Ran on a machine with Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz Fixes golang#17859 Change-Id: Iacc1e6b77e1aedba9447a6e94352606f131ea597 Reviewed-on: https://go-review.googlesource.com/76470 Reviewed-by: Marvin Stenger <marvin.stenger94@gmail.com> Reviewed-by: Joe Tsai <thebrokentoaster@gmail.com>
1 parent 506386f commit 65fbf0e

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

src/strings/strings.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,33 @@ func ToUpper(s string) string {
571571
}
572572

573573
// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case.
574-
func ToLower(s string) string { return Map(unicode.ToLower, s) }
574+
func ToLower(s string) string {
575+
isASCII, hasUpper := true, false
576+
for i := 0; i < len(s); i++ {
577+
c := s[i]
578+
if c >= utf8.RuneSelf {
579+
isASCII = false
580+
break
581+
}
582+
hasUpper = hasUpper || (c >= 'A' && c <= 'Z')
583+
}
584+
585+
if isASCII { // optimize for ASCII-only strings.
586+
if !hasUpper {
587+
return s
588+
}
589+
b := make([]byte, len(s))
590+
for i := 0; i < len(s); i++ {
591+
c := s[i]
592+
if c >= 'A' && c <= 'Z' {
593+
c += 'a' - 'A'
594+
}
595+
b[i] = c
596+
}
597+
return string(b)
598+
}
599+
return Map(unicode.ToLower, s)
600+
}
575601

576602
// ToTitle returns a copy of the string s with all Unicode letters mapped to their title case.
577603
func ToTitle(s string) string { return Map(unicode.ToTitle, s) }

src/strings/strings_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,8 @@ var lowerTests = []StringTest{
532532
{"abc", "abc"},
533533
{"AbC123", "abc123"},
534534
{"azAZ09_", "azaz09_"},
535+
{"longStrinGwitHmixofsmaLLandcAps", "longstringwithmixofsmallandcaps"},
536+
{"LONG\u2C6FSTRING\u2C6FWITH\u2C6FNONASCII\u2C6FCHARS", "long\u0250string\u0250with\u0250nonascii\u0250chars"},
535537
{"\u2C6D\u2C6D\u2C6D\u2C6D\u2C6D", "\u0251\u0251\u0251\u0251\u0251"}, // shrinks one byte per char
536538
}
537539

@@ -664,6 +666,19 @@ func BenchmarkToUpper(b *testing.B) {
664666
}
665667
}
666668

669+
func BenchmarkToLower(b *testing.B) {
670+
for _, tc := range lowerTests {
671+
b.Run(tc.in, func(b *testing.B) {
672+
for i := 0; i < b.N; i++ {
673+
actual := ToLower(tc.in)
674+
if actual != tc.out {
675+
b.Errorf("ToLower(%q) = %q; want %q", tc.in, actual, tc.out)
676+
}
677+
}
678+
})
679+
}
680+
}
681+
667682
func BenchmarkMapNoChanges(b *testing.B) {
668683
identity := func(r rune) rune {
669684
return r

0 commit comments

Comments
 (0)