aboutsummaryrefslogtreecommitdiff
path: root/src/runtime
diff options
context:
space:
mode:
authorMartin Möhrmann <martisch@uos.de>2016-08-26 15:00:46 +0200
committerMartin Möhrmann <martisch@uos.de>2016-08-30 18:17:20 +0000
commit0dae9dfb08a30983cce1114742c974077bdf5e18 (patch)
treeb625e6f0852c8285c528aa8499f18777c593c856 /src/runtime
parent0d7a2241cb684236f2728bb08514e7f256ac4a43 (diff)
downloadgo-0dae9dfb08a30983cce1114742c974077bdf5e18.tar.xz
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>
Diffstat (limited to 'src/runtime')
-rw-r--r--src/runtime/os_windows.go4
-rw-r--r--src/runtime/rune.go96
-rw-r--r--src/runtime/string.go47
-rw-r--r--src/runtime/string_test.go52
4 files changed, 77 insertions, 122 deletions
diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go
index 9147091a49..8529b35ca5 100644
--- a/src/runtime/os_windows.go
+++ b/src/runtime/os_windows.go
@@ -375,13 +375,11 @@ func writeConsole(handle uintptr, buf unsafe.Pointer, bufLen int32) int {
total := len(s)
w := 0
- for len(s) > 0 {
+ for _, r := range s {
if w >= len(utf16tmp)-2 {
writeConsoleUTF16(handle, utf16tmp[:w])
w = 0
}
- r, n := charntorune(s)
- s = s[n:]
if r < 0x10000 {
utf16tmp[w] = uint16(r)
w++
diff --git a/src/runtime/rune.go b/src/runtime/rune.go
index 91a0ca2503..84f7bbf1c0 100644
--- a/src/runtime/rune.go
+++ b/src/runtime/rune.go
@@ -49,115 +49,97 @@ const (
surrogateMin = 0xD800
surrogateMax = 0xDFFF
- bad = runeerror
-
runemax = 0x10FFFF /* maximum rune value */
)
-/*
- * Modified by Wei-Hwa Huang, Google Inc., on 2004-09-24
- * This is a slower but "safe" version of the old chartorune
- * that works on strings that are not necessarily null-terminated.
- *
- * If you know for sure that your string is null-terminated,
- * chartorune will be a bit faster.
- *
- * It is guaranteed not to attempt to access "length"
- * past the incoming pointer. This is to avoid
- * possible access violations. If the string appears to be
- * well-formed but incomplete (i.e., to get the whole Rune
- * we'd need to read past str+length) then we'll set the Rune
- * to Bad and return 0.
- *
- * Note that if we have decoding problems for other
- * reasons, we return 1 instead of 0.
- */
-func charntorune(s string) (rune, int) {
- /* When we're not allowed to read anything */
- if len(s) <= 0 {
- return bad, 1
+// charntorune returns the rune at the start of
+// s[k:] and the index after the rune in s.
+//
+// If the string appears to be incomplete or decoding problems
+// are encountered (runeerror, k + 1) is returned to ensure
+// progress when charntorune is used to iterate over a string.
+//
+// Modified by Wei-Hwa Huang, Google Inc., on 2004-09-24
+func charntorune(s string, k int) (rune, int) {
+ // When we're not allowed to read anything */
+ if len(s) <= k {
+ return runeerror, k + 1
}
- /*
- * one character sequence (7-bit value)
- * 00000-0007F => T1
- */
+ s = s[k:]
+
+ // one character sequence (7-bit value)
+ // 00000-0007F => T1
c := s[0]
if c < tx {
- return rune(c), 1
+ return rune(c), k + 1
}
// If we can't read more than one character we must stop
if len(s) <= 1 {
- return bad, 1
+ return runeerror, k + 1
}
- /*
- * two character sequence (11-bit value)
- * 0080-07FF => t2 tx
- */
+ // two character sequence (11-bit value)
+ // 0080-07FF => t2 tx
c1 := s[1] ^ tx
if (c1 & testx) != 0 {
- return bad, 1
+ return runeerror, k + 1
}
if c < t3 {
if c < t2 {
- return bad, 1
+ return runeerror, k + 1
}
l := ((rune(c) << bitx) | rune(c1)) & rune2
if l <= rune1 {
- return bad, 1
+ return runeerror, k + 1
}
- return l, 2
+ return l, k + 2
}
// If we can't read more than two characters we must stop
if len(s) <= 2 {
- return bad, 1
+ return runeerror, k + 1
}
- /*
- * three character sequence (16-bit value)
- * 0800-FFFF => t3 tx tx
- */
+ // three character sequence (16-bit value)
+ // 0800-FFFF => t3 tx tx
c2 := s[2] ^ tx
if (c2 & testx) != 0 {
- return bad, 1
+ return runeerror, k + 1
}
if c < t4 {
l := ((((rune(c) << bitx) | rune(c1)) << bitx) | rune(c2)) & rune3
if l <= rune2 {
- return bad, 1
+ return runeerror, k + 1
}
if surrogateMin <= l && l <= surrogateMax {
- return bad, 1
+ return runeerror, k + 1
}
- return l, 3
+ return l, k + 3
}
if len(s) <= 3 {
- return bad, 1
+ return runeerror, k + 1
}
- /*
- * four character sequence (21-bit value)
- * 10000-1FFFFF => t4 tx tx tx
- */
+ // four character sequence (21-bit value)
+ // 10000-1FFFFF => t4 tx tx tx
c3 := s[3] ^ tx
if (c3 & testx) != 0 {
- return bad, 1
+ return runeerror, k + 1
}
if c < t5 {
l := ((((((rune(c) << bitx) | rune(c1)) << bitx) | rune(c2)) << bitx) | rune(c3)) & rune4
if l <= rune3 || l > runemax {
- return bad, 1
+ return runeerror, k + 1
}
- return l, 4
+ return l, k + 4
}
// Support for 5-byte or longer UTF-8 would go here, but
- // since we don't have that, we'll just return bad.
- return bad, 1
+ // since we don't have that, we'll just return runeerror.
+ return runeerror, k + 1
}
// runetochar converts r to bytes and writes the result to str.
diff --git a/src/runtime/string.go b/src/runtime/string.go
index e74947f42f..5512f33ea8 100644
--- a/src/runtime/string.go
+++ b/src/runtime/string.go
@@ -163,12 +163,10 @@ func stringtoslicerune(buf *[tmpStringBufSize]rune, s string) []rune {
// two passes.
// unlike slicerunetostring, no race because strings are immutable.
n := 0
- t := s
- for len(s) > 0 {
- _, k := charntorune(s)
- s = s[k:]
+ for range s {
n++
}
+
var a []rune
if buf != nil && n <= len(buf) {
*buf = [tmpStringBufSize]rune{}
@@ -176,10 +174,9 @@ func stringtoslicerune(buf *[tmpStringBufSize]rune, s string) []rune {
} else {
a = rawruneslice(n)
}
+
n = 0
- for len(t) > 0 {
- r, k := charntorune(t)
- t = t[k:]
+ for _, r := range s {
a[n] = r
n++
}
@@ -244,42 +241,6 @@ func intstring(buf *[4]byte, v int64) string {
return s[:n]
}
-// stringiter returns the index of the next
-// rune after the rune that starts at s[k].
-func stringiter(s string, k int) int {
- if k >= len(s) {
- // 0 is end of iteration
- return 0
- }
-
- c := s[k]
- if c < runeself {
- return k + 1
- }
-
- // multi-char rune
- _, n := charntorune(s[k:])
- return k + n
-}
-
-// stringiter2 returns the rune that starts at s[k]
-// and the index where the next rune starts.
-func stringiter2(s string, k int) (int, rune) {
- if k >= len(s) {
- // 0 is end of iteration
- return 0, 0
- }
-
- c := s[k]
- if c < runeself {
- return k + 1, rune(c)
- }
-
- // multi-char rune
- r, n := charntorune(s[k:])
- return k + n, r
-}
-
// rawstring allocates storage for a new string. The returned
// string and byte slice both refer to the same storage.
// The storage is not zeroed. Callers should use
diff --git a/src/runtime/string_test.go b/src/runtime/string_test.go
index 0f1d82a481..b1757f0721 100644
--- a/src/runtime/string_test.go
+++ b/src/runtime/string_test.go
@@ -82,28 +82,42 @@ func BenchmarkCompareStringBig(b *testing.B) {
b.SetBytes(int64(len(s1)))
}
-func BenchmarkRuneIterate(b *testing.B) {
- bytes := make([]byte, 100)
- for i := range bytes {
- bytes[i] = byte('A')
- }
- s := string(bytes)
- for i := 0; i < b.N; i++ {
- for range s {
- }
- }
+var stringdata = []struct{ name, data string }{
+ {"ASCII", "01234567890"},
+ {"Japanese", "日本語日本語日本語"},
}
-func BenchmarkRuneIterate2(b *testing.B) {
- bytes := make([]byte, 100)
- for i := range bytes {
- bytes[i] = byte('A')
- }
- s := string(bytes)
- for i := 0; i < b.N; i++ {
- for range s {
+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 {
+ }
+ }
+ })
}
- }
+ })
+ 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) {