1
0
mirror of https://github.com/golang/go synced 2024-11-19 15:05:00 -07:00
go/src/runtime/string_test.go

391 lines
8.2 KiB
Go
Raw Normal View History

// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"runtime"
"strconv"
"strings"
"testing"
)
// Strings and slices that don't escape and fit into tmpBuf are stack allocated,
// which defeats using AllocsPerRun to test other optimizations.
const sizeNoStack = 100
func BenchmarkCompareStringEqual(b *testing.B) {
bytes := []byte("Hello Gophers!")
s1, s2 := string(bytes), string(bytes)
for i := 0; i < b.N; i++ {
if s1 != s2 {
b.Fatal("s1 != s2")
}
}
}
func BenchmarkCompareStringIdentical(b *testing.B) {
s1 := "Hello Gophers!"
s2 := s1
for i := 0; i < b.N; i++ {
if s1 != s2 {
b.Fatal("s1 != s2")
}
}
}
func BenchmarkCompareStringSameLength(b *testing.B) {
s1 := "Hello Gophers!"
s2 := "Hello, Gophers"
for i := 0; i < b.N; i++ {
if s1 == s2 {
b.Fatal("s1 == s2")
}
}
}
func BenchmarkCompareStringDifferentLength(b *testing.B) {
s1 := "Hello Gophers!"
s2 := "Hello, Gophers!"
for i := 0; i < b.N; i++ {
if s1 == s2 {
b.Fatal("s1 == s2")
}
}
}
func BenchmarkCompareStringBigUnaligned(b *testing.B) {
bytes := make([]byte, 0, 1<<20)
for len(bytes) < 1<<20 {
bytes = append(bytes, "Hello Gophers!"...)
}
s1, s2 := string(bytes), "hello"+string(bytes)
for i := 0; i < b.N; i++ {
if s1 != s2[len("hello"):] {
b.Fatal("s1 != s2")
}
}
b.SetBytes(int64(len(s1)))
}
func BenchmarkCompareStringBig(b *testing.B) {
bytes := make([]byte, 0, 1<<20)
for len(bytes) < 1<<20 {
bytes = append(bytes, "Hello Gophers!"...)
}
s1, s2 := string(bytes), string(bytes)
for i := 0; i < b.N; i++ {
if s1 != s2 {
b.Fatal("s1 != s2")
}
}
b.SetBytes(int64(len(s1)))
}
func BenchmarkConcatStringAndBytes(b *testing.B) {
s1 := []byte("Gophers!")
for i := 0; i < b.N; i++ {
_ = "Hello " + string(s1)
}
}
var escapeString string
func BenchmarkSliceByteToString(b *testing.B) {
buf := []byte{'!'}
for n := 0; n < 8; n++ {
b.Run(strconv.Itoa(len(buf)), func(b *testing.B) {
for i := 0; i < b.N; i++ {
escapeString = string(buf)
}
})
buf = append(buf, buf...)
}
}
cmd/compile: improve string iteration performance Generate a for loop for ranging over strings that only needs to call the runtime function charntorune for non ASCII characters. This provides faster iteration over ASCII characters and slightly faster iteration for other characters. The runtime function charntorune is changed to take an index from where to start decoding and returns the index after the last byte belonging to the decoded rune. All call sites of charntorune in the runtime are replaced by a for loop that will be transformed by the compiler instead of calling the charntorune function directly. go binary size decreases by 80 bytes. godoc binary size increases by around 4 kilobytes. runtime: name old time/op new time/op delta RuneIterate/range/ASCII-4 43.7ns ± 3% 10.3ns ± 4% -76.33% (p=0.000 n=44+45) RuneIterate/range/Japanese-4 72.5ns ± 2% 62.8ns ± 2% -13.41% (p=0.000 n=49+50) RuneIterate/range1/ASCII-4 43.5ns ± 2% 10.4ns ± 3% -76.18% (p=0.000 n=50+50) RuneIterate/range1/Japanese-4 72.5ns ± 2% 62.9ns ± 2% -13.26% (p=0.000 n=50+49) RuneIterate/range2/ASCII-4 43.5ns ± 3% 10.3ns ± 2% -76.22% (p=0.000 n=48+47) RuneIterate/range2/Japanese-4 72.4ns ± 2% 62.7ns ± 2% -13.47% (p=0.000 n=50+50) strings: name old time/op new time/op delta IndexRune-4 64.7ns ± 5% 22.4ns ± 3% -65.43% (p=0.000 n=25+21) MapNoChanges-4 269ns ± 2% 157ns ± 2% -41.46% (p=0.000 n=23+24) Fields-4 23.0ms ± 2% 19.7ms ± 2% -14.35% (p=0.000 n=25+25) FieldsFunc-4 23.1ms ± 2% 19.6ms ± 2% -14.94% (p=0.000 n=25+24) name old speed new speed delta Fields-4 45.6MB/s ± 2% 53.2MB/s ± 2% +16.87% (p=0.000 n=24+25) FieldsFunc-4 45.5MB/s ± 2% 53.5MB/s ± 2% +17.57% (p=0.000 n=25+24) Updates #13162 Change-Id: I79ffaf828d82bf9887592f08e5cad883e9f39701 Reviewed-on: https://go-review.googlesource.com/27853 TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com> Run-TryBot: Martin Möhrmann <martisch@uos.de>
2016-08-26 07:00:46 -06:00
var stringdata = []struct{ name, data string }{
{"ASCII", "01234567890"},
{"Japanese", "日本語日本語日本語"},
{"MixedLength", "$Ѐࠀက퀀𐀀\U00040000\U0010FFFF"},
}
cmd/compile: improve string iteration performance Generate a for loop for ranging over strings that only needs to call the runtime function charntorune for non ASCII characters. This provides faster iteration over ASCII characters and slightly faster iteration for other characters. The runtime function charntorune is changed to take an index from where to start decoding and returns the index after the last byte belonging to the decoded rune. All call sites of charntorune in the runtime are replaced by a for loop that will be transformed by the compiler instead of calling the charntorune function directly. go binary size decreases by 80 bytes. godoc binary size increases by around 4 kilobytes. runtime: name old time/op new time/op delta RuneIterate/range/ASCII-4 43.7ns ± 3% 10.3ns ± 4% -76.33% (p=0.000 n=44+45) RuneIterate/range/Japanese-4 72.5ns ± 2% 62.8ns ± 2% -13.41% (p=0.000 n=49+50) RuneIterate/range1/ASCII-4 43.5ns ± 2% 10.4ns ± 3% -76.18% (p=0.000 n=50+50) RuneIterate/range1/Japanese-4 72.5ns ± 2% 62.9ns ± 2% -13.26% (p=0.000 n=50+49) RuneIterate/range2/ASCII-4 43.5ns ± 3% 10.3ns ± 2% -76.22% (p=0.000 n=48+47) RuneIterate/range2/Japanese-4 72.4ns ± 2% 62.7ns ± 2% -13.47% (p=0.000 n=50+50) strings: name old time/op new time/op delta IndexRune-4 64.7ns ± 5% 22.4ns ± 3% -65.43% (p=0.000 n=25+21) MapNoChanges-4 269ns ± 2% 157ns ± 2% -41.46% (p=0.000 n=23+24) Fields-4 23.0ms ± 2% 19.7ms ± 2% -14.35% (p=0.000 n=25+25) FieldsFunc-4 23.1ms ± 2% 19.6ms ± 2% -14.94% (p=0.000 n=25+24) name old speed new speed delta Fields-4 45.6MB/s ± 2% 53.2MB/s ± 2% +16.87% (p=0.000 n=24+25) FieldsFunc-4 45.5MB/s ± 2% 53.5MB/s ± 2% +17.57% (p=0.000 n=25+24) Updates #13162 Change-Id: I79ffaf828d82bf9887592f08e5cad883e9f39701 Reviewed-on: https://go-review.googlesource.com/27853 TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com> Run-TryBot: Martin Möhrmann <martisch@uos.de>
2016-08-26 07:00:46 -06:00
func BenchmarkRuneIterate(b *testing.B) {
b.Run("range", func(b *testing.B) {
for _, sd := range stringdata {
b.Run(sd.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
for range sd.data {
}
}
})
}
cmd/compile: improve string iteration performance Generate a for loop for ranging over strings that only needs to call the runtime function charntorune for non ASCII characters. This provides faster iteration over ASCII characters and slightly faster iteration for other characters. The runtime function charntorune is changed to take an index from where to start decoding and returns the index after the last byte belonging to the decoded rune. All call sites of charntorune in the runtime are replaced by a for loop that will be transformed by the compiler instead of calling the charntorune function directly. go binary size decreases by 80 bytes. godoc binary size increases by around 4 kilobytes. runtime: name old time/op new time/op delta RuneIterate/range/ASCII-4 43.7ns ± 3% 10.3ns ± 4% -76.33% (p=0.000 n=44+45) RuneIterate/range/Japanese-4 72.5ns ± 2% 62.8ns ± 2% -13.41% (p=0.000 n=49+50) RuneIterate/range1/ASCII-4 43.5ns ± 2% 10.4ns ± 3% -76.18% (p=0.000 n=50+50) RuneIterate/range1/Japanese-4 72.5ns ± 2% 62.9ns ± 2% -13.26% (p=0.000 n=50+49) RuneIterate/range2/ASCII-4 43.5ns ± 3% 10.3ns ± 2% -76.22% (p=0.000 n=48+47) RuneIterate/range2/Japanese-4 72.4ns ± 2% 62.7ns ± 2% -13.47% (p=0.000 n=50+50) strings: name old time/op new time/op delta IndexRune-4 64.7ns ± 5% 22.4ns ± 3% -65.43% (p=0.000 n=25+21) MapNoChanges-4 269ns ± 2% 157ns ± 2% -41.46% (p=0.000 n=23+24) Fields-4 23.0ms ± 2% 19.7ms ± 2% -14.35% (p=0.000 n=25+25) FieldsFunc-4 23.1ms ± 2% 19.6ms ± 2% -14.94% (p=0.000 n=25+24) name old speed new speed delta Fields-4 45.6MB/s ± 2% 53.2MB/s ± 2% +16.87% (p=0.000 n=24+25) FieldsFunc-4 45.5MB/s ± 2% 53.5MB/s ± 2% +17.57% (p=0.000 n=25+24) Updates #13162 Change-Id: I79ffaf828d82bf9887592f08e5cad883e9f39701 Reviewed-on: https://go-review.googlesource.com/27853 TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com> Run-TryBot: Martin Möhrmann <martisch@uos.de>
2016-08-26 07:00:46 -06:00
})
b.Run("range1", func(b *testing.B) {
for _, sd := range stringdata {
b.Run(sd.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
for _ = range sd.data {
}
}
})
}
})
b.Run("range2", func(b *testing.B) {
for _, sd := range stringdata {
b.Run(sd.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, _ = range sd.data {
}
}
})
}
})
}
func BenchmarkArrayEqual(b *testing.B) {
a1 := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
a2 := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
b.ResetTimer()
for i := 0; i < b.N; i++ {
if a1 != a2 {
b.Fatal("not equal")
}
}
}
func TestStringW(t *testing.T) {
strings := []string{
"hello",
"a\u5566\u7788b",
}
for _, s := range strings {
var b []uint16
for _, c := range s {
b = append(b, uint16(c))
if c != rune(uint16(c)) {
t.Errorf("bad test: stringW can't handle >16 bit runes")
}
}
b = append(b, 0)
r := runtime.GostringW(b)
if r != s {
t.Errorf("gostringW(%v) = %s, want %s", b, r, s)
}
}
}
func TestLargeStringConcat(t *testing.T) {
output := runTestProg(t, "testprog", "stringconcat")
want := "panic: " + strings.Repeat("0", 1<<10) + strings.Repeat("1", 1<<10) +
strings.Repeat("2", 1<<10) + strings.Repeat("3", 1<<10)
if !strings.HasPrefix(output, want) {
t.Fatalf("output does not start with %q:\n%s", want, output)
}
}
func TestCompareTempString(t *testing.T) {
s := strings.Repeat("x", sizeNoStack)
b := []byte(s)
n := testing.AllocsPerRun(1000, func() {
if string(b) != s {
t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
}
if string(b) == s {
} else {
t.Fatalf("strings are not equal: '%v' and '%v'", string(b), s)
}
})
if n != 0 {
t.Fatalf("want 0 allocs, got %v", n)
}
}
cmd/gc: allocate buffers for non-escaped strings on stack Currently we always allocate string buffers in heap. For example, in the following code we allocate a temp string just for comparison: if string(byteSlice) == "abc" { ... } This change extends escape analysis to cover []byte->string conversions and string concatenation. If the result of operations does not escape, compiler allocates a small buffer on stack and passes it to slicebytetostring and concatstrings. Then runtime uses the buffer if the result fits into it. Size of the buffer is 32 bytes. There is no fundamental theory behind this number. Just an observation that on std lib tests/benchmarks frequency of string allocation is inversely proportional to string length; and there is significant number of allocations up to length 32. benchmark old allocs new allocs delta BenchmarkFprintfBytes 2 1 -50.00% BenchmarkDecodeComplex128Slice 318 316 -0.63% BenchmarkDecodeFloat64Slice 318 316 -0.63% BenchmarkDecodeInt32Slice 318 316 -0.63% BenchmarkDecodeStringSlice 2318 2316 -0.09% BenchmarkStripTags 11 5 -54.55% BenchmarkDecodeGray 111 102 -8.11% BenchmarkDecodeNRGBAGradient 200 188 -6.00% BenchmarkDecodeNRGBAOpaque 165 152 -7.88% BenchmarkDecodePaletted 319 309 -3.13% BenchmarkDecodeRGB 166 157 -5.42% BenchmarkDecodeInterlacing 279 268 -3.94% BenchmarkGoLookupIP 153 135 -11.76% BenchmarkGoLookupIPNoSuchHost 508 466 -8.27% BenchmarkGoLookupIPWithBrokenNameServer 245 226 -7.76% BenchmarkClientServerParallel4 62 61 -1.61% BenchmarkClientServerParallel64 62 61 -1.61% BenchmarkClientServerParallelTLS4 79 78 -1.27% BenchmarkClientServerParallelTLS64 112 111 -0.89% benchmark old ns/op new ns/op delta BenchmarkFprintfBytes 381 311 -18.37% BenchmarkStripTags 2615 2351 -10.10% BenchmarkDecodeNRGBAGradient 3715887 3635096 -2.17% BenchmarkDecodeNRGBAOpaque 3047645 2928644 -3.90% BenchmarkGoLookupIP 153 135 -11.76% BenchmarkGoLookupIPNoSuchHost 508 466 -8.27% Change-Id: I9ec01da816945c3329d7be3c7794b520418c3f99 Reviewed-on: https://go-review.googlesource.com/3120 Reviewed-by: Keith Randall <khr@golang.org> Reviewed-by: Russ Cox <rsc@golang.org>
2015-01-21 07:37:59 -07:00
func TestStringOnStack(t *testing.T) {
s := ""
for i := 0; i < 3; i++ {
s = "a" + s + "b" + s + "c"
}
if want := "aaabcbabccbaabcbabccc"; s != want {
t.Fatalf("want: '%v', got '%v'", want, s)
}
}
func TestIntString(t *testing.T) {
// Non-escaping result of intstring.
s := ""
for i := 0; i < 4; i++ {
s += string(i+'0') + string(i+'0'+1)
}
if want := "01122334"; s != want {
t.Fatalf("want '%v', got '%v'", want, s)
}
// Escaping result of intstring.
var a [4]string
for i := 0; i < 4; i++ {
a[i] = string(i + '0')
}
s = a[0] + a[1] + a[2] + a[3]
if want := "0123"; s != want {
t.Fatalf("want '%v', got '%v'", want, s)
}
}
func TestIntStringAllocs(t *testing.T) {
unknown := '0'
n := testing.AllocsPerRun(1000, func() {
s1 := string(unknown)
s2 := string(unknown + 1)
if s1 == s2 {
t.Fatalf("bad")
}
})
if n != 0 {
t.Fatalf("want 0 allocs, got %v", n)
}
}
func TestRangeStringCast(t *testing.T) {
s := strings.Repeat("x", sizeNoStack)
n := testing.AllocsPerRun(1000, func() {
for i, c := range []byte(s) {
if c != s[i] {
t.Fatalf("want '%c' at pos %v, got '%c'", s[i], i, c)
}
}
})
if n != 0 {
t.Fatalf("want 0 allocs, got %v", n)
}
}
func isZeroed(b []byte) bool {
for _, x := range b {
if x != 0 {
return false
}
}
return true
}
func isZeroedR(r []rune) bool {
for _, x := range r {
if x != 0 {
return false
}
}
return true
}
func TestString2Slice(t *testing.T) {
// Make sure we don't return slices that expose
// an unzeroed section of stack-allocated temp buf
// between len and cap. See issue 14232.
s := "foož"
b := ([]byte)(s)
if !isZeroed(b[len(b):cap(b)]) {
t.Errorf("extra bytes not zeroed")
}
r := ([]rune)(s)
if !isZeroedR(r[len(r):cap(r)]) {
t.Errorf("extra runes not zeroed")
}
}
const intSize = 32 << (^uint(0) >> 63)
type atoi64Test struct {
in string
out int64
ok bool
}
var atoi64tests = []atoi64Test{
{"", 0, false},
{"0", 0, true},
{"-0", 0, true},
{"1", 1, true},
{"-1", -1, true},
{"12345", 12345, true},
{"-12345", -12345, true},
{"012345", 12345, true},
{"-012345", -12345, true},
{"12345x", 0, false},
{"-12345x", 0, false},
{"98765432100", 98765432100, true},
{"-98765432100", -98765432100, true},
{"20496382327982653440", 0, false},
{"-20496382327982653440", 0, false},
{"9223372036854775807", 1<<63 - 1, true},
{"-9223372036854775807", -(1<<63 - 1), true},
{"9223372036854775808", 0, false},
{"-9223372036854775808", -1 << 63, true},
{"9223372036854775809", 0, false},
{"-9223372036854775809", 0, false},
}
func TestAtoi(t *testing.T) {
switch intSize {
case 32:
for i := range atoi32tests {
test := &atoi32tests[i]
out, ok := runtime.Atoi(test.in)
if test.out != int32(out) || test.ok != ok {
t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
test.in, out, ok, test.out, test.ok)
}
}
case 64:
for i := range atoi64tests {
test := &atoi64tests[i]
out, ok := runtime.Atoi(test.in)
if test.out != int64(out) || test.ok != ok {
t.Errorf("atoi(%q) = (%v, %v) want (%v, %v)",
test.in, out, ok, test.out, test.ok)
}
}
}
}
type atoi32Test struct {
in string
out int32
ok bool
}
var atoi32tests = []atoi32Test{
{"", 0, false},
{"0", 0, true},
{"-0", 0, true},
{"1", 1, true},
{"-1", -1, true},
{"12345", 12345, true},
{"-12345", -12345, true},
{"012345", 12345, true},
{"-012345", -12345, true},
{"12345x", 0, false},
{"-12345x", 0, false},
{"987654321", 987654321, true},
{"-987654321", -987654321, true},
{"2147483647", 1<<31 - 1, true},
{"-2147483647", -(1<<31 - 1), true},
{"2147483648", 0, false},
{"-2147483648", -1 << 31, true},
{"2147483649", 0, false},
{"-2147483649", 0, false},
}
func TestAtoi32(t *testing.T) {
for i := range atoi32tests {
test := &atoi32tests[i]
out, ok := runtime.Atoi32(test.in)
if test.out != out || test.ok != ok {
t.Errorf("atoi32(%q) = (%v, %v) want (%v, %v)",
test.in, out, ok, test.out, test.ok)
}
}
}