mirror of
https://github.com/golang/go
synced 2024-11-20 05:14:41 -07:00
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
This commit is contained in:
parent
43512e6c70
commit
a7a854b82f
@ -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 {
|
|||||||
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
|
|||||||
|
|
||||||
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) {
|
|||||||
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"
|
||||||
|
Loading…
Reference in New Issue
Block a user