]> Cypherpunks repositories - gostls13.git/commitdiff
strings: Map: avoid allocation when string is unchanged
authorBrad Fitzpatrick <bradfitz@golang.org>
Mon, 28 Mar 2011 16:41:57 +0000 (09:41 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Mon, 28 Mar 2011 16:41:57 +0000 (09:41 -0700)
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
https://golang.org/cl/4306056

src/pkg/strings/strings.go
src/pkg/strings/strings_test.go

index 5f009e54859146fb2d17345686758006d496277b..44dcf99b652a893a0207808d24c006860cddc356 100644 (file)
@@ -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])
 }
 
index d75f1ad9c65edda1fb83c5f352d3027068be83e9..c45b1485d8fffdb65e30e59740f557a6d5b85f98 100644 (file)
@@ -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"