Commit 71be0138 authored by Dmitry Vyukov's avatar Dmitry Vyukov

cmd/gc: don't copy string in range []byte(str)

Using benchmark from the issue:

benchmark                    old ns/op     new ns/op     delta
BenchmarkRangeStringCast     2162          1152          -46.72%

benchmark                    old allocs     new allocs     delta
BenchmarkRangeStringCast     1              0              -100.00%

Fixes #2204

Change-Id: I92c5edd2adca4a7b6fba00713a581bf49dc59afe
Reviewed-on: https://go-review.googlesource.com/3790Reviewed-by: 's avatarKeith Randall <khr@golang.org>
parent 70321df0
......@@ -38,6 +38,7 @@ char *runtimeimport =
"func @\"\".slicebytetostringtmp (? []byte) (? string)\n"
"func @\"\".slicerunetostring (? []rune) (? string)\n"
"func @\"\".stringtoslicebyte (? string) (? []byte)\n"
"func @\"\".stringtoslicebytetmp (? string) (? []byte)\n"
"func @\"\".stringtoslicerune (? string) (? []rune)\n"
"func @\"\".stringiter (? string, ? int) (? int)\n"
"func @\"\".stringiter2 (? string, ? int) (@\"\".retk·1 int, @\"\".retv·2 rune)\n"
......
......@@ -467,6 +467,7 @@ enum
OARRAYBYTESTRTMP, // string(bytes) ephemeral
OARRAYRUNESTR, // string(runes)
OSTRARRAYBYTE, // []byte(s)
OSTRARRAYBYTETMP, // []byte(s) ephemeral
OSTRARRAYRUNE, // []rune(s)
OAS, // x = y or x := y
OAS2, // x, y, z = xx, yy, zz
......
......@@ -757,6 +757,10 @@ orderstmt(Node *n, Order *order)
default:
fatal("orderstmt range %T", n->type);
case TARRAY:
// Mark []byte(str) range expression to reuse string backing storage.
// It is safe because the storage cannot be mutated.
if(n->right->op == OSTRARRAYBYTE)
n->right->op = OSTRARRAYBYTETMP;
if(count(n->list) < 2 || isblank(n->list->next->n)) {
// for i := range x will only use x once, to compute len(x).
// No need to copy it.
......
......@@ -52,6 +52,7 @@ func slicebytetostring(*[32]byte, []byte) string
func slicebytetostringtmp([]byte) string
func slicerunetostring([]rune) string
func stringtoslicebyte(string) []byte
func stringtoslicebytetmp(string) []byte
func stringtoslicerune(string) []rune
func stringiter(string, int) int
func stringiter2(string, int) (retk int, retv rune)
......
......@@ -1406,6 +1406,11 @@ walkexpr(Node **np, NodeList **init)
n = mkcall("stringtoslicebyte", n->type, init, conv(n->left, types[TSTRING]));
goto ret;
case OSTRARRAYBYTETMP:
// stringtoslicebytetmp(string) []byte;
n = mkcall("stringtoslicebytetmp", n->type, init, conv(n->left, types[TSTRING]));
goto ret;
case OSTRARRAYRUNE:
// stringtoslicerune(string) []rune
n = mkcall("stringtoslicerune", n->type, init, n->left);
......
......@@ -135,6 +135,18 @@ func stringtoslicebyte(s string) []byte {
return b
}
func stringtoslicebytetmp(s string) []byte {
// Return a slice referring to the actual string bytes.
// This is only for use by internal compiler optimizations
// that know that the slice won't be mutated.
// The only such case today is:
// for i, c := range []byte(str)
str := (*stringStruct)(unsafe.Pointer(&s))
ret := slice{array: (*byte)(str.str), len: uint(str.len), cap: uint(str.len)}
return *(*[]byte)(unsafe.Pointer(&ret))
}
func stringtoslicerune(s string) []rune {
// two passes.
// unlike slicerunetostring, no race because strings are immutable.
......
......@@ -221,3 +221,17 @@ func TestIntStringAllocs(t *testing.T) {
t.Fatalf("want 0 allocs, got %v", n)
}
}
func TestRangeStringCast(t *testing.T) {
s := "abc"
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)
}
}
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