diff options
author | Brad Fitzpatrick <bradfitz@golang.org> | 2011-03-28 09:41:57 -0700 |
---|---|---|
committer | Brad Fitzpatrick <bradfitz@golang.org> | 2011-03-28 09:41:57 -0700 |
commit | 6cbe95edc2e1c3106301ab1482a8786764362fd0 (patch) | |
tree | e31d5f6998a7a910c311843d30606e56ff144379 /src/pkg/strings | |
parent | fe2678e302f50a55f541c53855c25e1bb25f61e9 (diff) | |
download | go-6cbe95edc2e1c3106301ab1482a8786764362fd0.tar.gz |
strings: Map: avoid allocation when string is unchanged
This speeds up strings.ToLower, etc.
before/after:
strings_test.BenchmarkMapNoChanges 1000000 1013 ns/op
strings_test.BenchmarkMapNoChanges 5000000 442 ns/op
R=r, rog, eh, rsc
CC=golang-dev
http://codereview.appspot.com/4306056
Committer: Brad Fitzpatrick <bradfitz@golang.org>
Diffstat (limited to 'src/pkg/strings')
-rw-r--r-- | src/pkg/strings/strings.go | 17 | ||||
-rw-r--r-- | src/pkg/strings/strings_test.go | 22 |
2 files changed, 37 insertions, 2 deletions
diff --git a/src/pkg/strings/strings.go b/src/pkg/strings/strings.go index 5f009e548..44dcf99b6 100644 --- a/src/pkg/strings/strings.go +++ b/src/pkg/strings/strings.go @@ -312,9 +312,19 @@ func Map(mapping func(rune int) int, s string) string { // fine. It could also shrink but that falls out naturally. maxbytes := len(s) // length of b nbytes := 0 // number of bytes encoded in b - b := make([]byte, maxbytes) - for _, c := range s { + // The output buffer b is initialized on demand, the first + // time a character differs. + var b []byte + + for i, c := range s { rune := mapping(c) + if b == nil { + if rune == c { + continue + } + b = make([]byte, maxbytes) + nbytes = copy(b, s[:i]) + } if rune >= 0 { wid := 1 if rune >= utf8.RuneSelf { @@ -330,6 +340,9 @@ func Map(mapping func(rune int) int, s string) string { nbytes += utf8.EncodeRune(b[nbytes:maxbytes], rune) } } + if b == nil { + return s + } return string(b[0:nbytes]) } diff --git a/src/pkg/strings/strings_test.go b/src/pkg/strings/strings_test.go index d75f1ad9c..c45b1485d 100644 --- a/src/pkg/strings/strings_test.go +++ b/src/pkg/strings/strings_test.go @@ -6,10 +6,12 @@ package strings_test import ( "os" + "reflect" "strconv" . "strings" "testing" "unicode" + "unsafe" "utf8" ) @@ -429,12 +431,32 @@ func TestMap(t *testing.T) { if m != expect { t.Errorf("drop: expected %q got %q", expect, m) } + + // 6. Identity + identity := func(rune int) int { + return rune + } + orig := "Input string that we expect not to be copied." + m = Map(identity, orig) + if (*reflect.StringHeader)(unsafe.Pointer(&orig)).Data != + (*reflect.StringHeader)(unsafe.Pointer(&m)).Data { + t.Error("unexpected copy during identity map") + } } func TestToUpper(t *testing.T) { runStringTests(t, ToUpper, "ToUpper", upperTests) } func TestToLower(t *testing.T) { runStringTests(t, ToLower, "ToLower", lowerTests) } +func BenchmarkMapNoChanges(b *testing.B) { + identity := func(rune int) int { + return rune + } + for i := 0; i < b.N; i++ { + Map(identity, "Some string that won't be modified.") + } +} + func TestSpecialCase(t *testing.T) { lower := "abcçdefgğhıijklmnoöprsştuüvyz" upper := "ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ" |