Commit a7a854b8 authored by Brad Fitzpatrick's avatar Brad Fitzpatrick

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
https://golang.org/cl/4306056
parent 43512e6c
...@@ -312,9 +312,19 @@ func Map(mapping func(rune int) int, s string) string { ...@@ -312,9 +312,19 @@ func Map(mapping func(rune int) int, s string) string {
// fine. It could also shrink but that falls out naturally. // fine. It could also shrink but that falls out naturally.
maxbytes := len(s) // length of b maxbytes := len(s) // length of b
nbytes := 0 // number of bytes encoded in b nbytes := 0 // number of bytes encoded in b
b := make([]byte, maxbytes) // The output buffer b is initialized on demand, the first
for _, c := range s { // time a character differs.
var b []byte
for i, c := range s {
rune := mapping(c) rune := mapping(c)
if b == nil {
if rune == c {
continue
}
b = make([]byte, maxbytes)
nbytes = copy(b, s[:i])
}
if rune >= 0 { if rune >= 0 {
wid := 1 wid := 1
if rune >= utf8.RuneSelf { if rune >= utf8.RuneSelf {
...@@ -330,6 +340,9 @@ func Map(mapping func(rune int) int, s string) string { ...@@ -330,6 +340,9 @@ func Map(mapping func(rune int) int, s string) string {
nbytes += utf8.EncodeRune(b[nbytes:maxbytes], rune) nbytes += utf8.EncodeRune(b[nbytes:maxbytes], rune)
} }
} }
if b == nil {
return s
}
return string(b[0:nbytes]) return string(b[0:nbytes])
} }
......
...@@ -6,10 +6,12 @@ package strings_test ...@@ -6,10 +6,12 @@ package strings_test
import ( import (
"os" "os"
"reflect"
"strconv" "strconv"
. "strings" . "strings"
"testing" "testing"
"unicode" "unicode"
"unsafe"
"utf8" "utf8"
) )
...@@ -429,12 +431,32 @@ func TestMap(t *testing.T) { ...@@ -429,12 +431,32 @@ func TestMap(t *testing.T) {
if m != expect { if m != expect {
t.Errorf("drop: expected %q got %q", expect, m) 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 TestToUpper(t *testing.T) { runStringTests(t, ToUpper, "ToUpper", upperTests) }
func TestToLower(t *testing.T) { runStringTests(t, ToLower, "ToLower", lowerTests) } 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) { func TestSpecialCase(t *testing.T) {
lower := "abcçdefgğhıijklmnoöprsştuüvyz" lower := "abcçdefgğhıijklmnoöprsştuüvyz"
upper := "ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ" upper := "ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment