diff options
Diffstat (limited to 'src/runtime')
| -rw-r--r-- | src/runtime/string.go | 72 | ||||
| -rw-r--r-- | src/runtime/string_test.go | 11 |
2 files changed, 68 insertions, 15 deletions
diff --git a/src/runtime/string.go b/src/runtime/string.go index 6f7de4de1e..9ec6f320eb 100644 --- a/src/runtime/string.go +++ b/src/runtime/string.go @@ -8,7 +8,18 @@ import ( "unsafe" ) -func concatstrings(a []string) string { +// The constant is known to the compiler. +// There is no fundamental theory behind this number. +const tmpStringBufSize = 32 + +type tmpBuf [tmpStringBufSize]byte + +// concatstrings implements a Go string concatenation x+y+z+... +// The operands are passed in the slice a. +// If buf != nil, the compiler has determined that the result does not +// escape the calling function, so the string data can be stored in buf +// if small enough. +func concatstrings(buf *tmpBuf, a []string) string { idx := 0 l := 0 count := 0 @@ -27,10 +38,14 @@ func concatstrings(a []string) string { if count == 0 { return "" } - if count == 1 { + + // If there is just one string and either it is not on the stack + // or our result does not escape the calling frame (buf != nil), + // then we can return that string directly. + if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) { return a[idx] } - s, b := rawstring(l) + s, b := rawstringtmp(buf, l) l = 0 for _, x := range a { copy(b[l:], x) @@ -39,34 +54,61 @@ func concatstrings(a []string) string { return s } -func concatstring2(a [2]string) string { - return concatstrings(a[:]) +func concatstring2(buf *tmpBuf, a [2]string) string { + return concatstrings(buf, a[:]) } -func concatstring3(a [3]string) string { - return concatstrings(a[:]) +func concatstring3(buf *tmpBuf, a [3]string) string { + return concatstrings(buf, a[:]) } -func concatstring4(a [4]string) string { - return concatstrings(a[:]) +func concatstring4(buf *tmpBuf, a [4]string) string { + return concatstrings(buf, a[:]) } -func concatstring5(a [5]string) string { - return concatstrings(a[:]) +func concatstring5(buf *tmpBuf, a [5]string) string { + return concatstrings(buf, a[:]) } -func slicebytetostring(b []byte) string { - if raceenabled && len(b) > 0 { +// Buf is a fixed-size buffer for the result, +// it is not nil if the result does not escape. +func slicebytetostring(buf *tmpBuf, b []byte) string { + l := len(b) + if l == 0 { + // Turns out to be a relatively common case. + // Consider that you want to parse out data between parens in "foo()bar", + // you find the indices and convert the subslice to string. + return "" + } + if raceenabled && l > 0 { racereadrangepc(unsafe.Pointer(&b[0]), - uintptr(len(b)), + uintptr(l), getcallerpc(unsafe.Pointer(&b)), funcPC(slicebytetostring)) } - s, c := rawstring(len(b)) + s, c := rawstringtmp(buf, l) copy(c, b) return s } +// stringDataOnStack reports whether the string's data is +// stored on the current goroutine's stack. +func stringDataOnStack(s string) bool { + ptr := uintptr((*stringStruct)(unsafe.Pointer(&s)).str) + stk := getg().stack + return stk.lo <= ptr && ptr < stk.hi +} + +func rawstringtmp(buf *tmpBuf, l int) (s string, b []byte) { + if buf != nil && l <= len(buf) { + b = buf[:l] + s = slicebytetostringtmp(b) + } else { + s, b = rawstring(l) + } + return +} + func slicebytetostringtmp(b []byte) string { // Return a "string" referring to the actual []byte bytes. // This is only for use by internal compiler optimizations diff --git a/src/runtime/string_test.go b/src/runtime/string_test.go index 03c8948467..249f431e18 100644 --- a/src/runtime/string_test.go +++ b/src/runtime/string_test.go @@ -175,3 +175,14 @@ func TestCompareTempString(t *testing.T) { t.Fatalf("want 0 allocs, got %v", n) } } + +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) + } +} |
