]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/gc: don't copy string in range []byte(str)
authorDmitry Vyukov <dvyukov@google.com>
Tue, 3 Feb 2015 17:50:58 +0000 (20:50 +0300)
committerDmitry Vyukov <dvyukov@google.com>
Wed, 4 Feb 2015 04:37:21 +0000 (04:37 +0000)
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/3790
Reviewed-by: Keith Randall <khr@golang.org>
src/cmd/gc/builtin.c
src/cmd/gc/go.h
src/cmd/gc/order.c
src/cmd/gc/runtime.go
src/cmd/gc/walk.c
src/runtime/string.go
src/runtime/string_test.go

index fcd5685cdce0d60aac394082bce123ae360da21e..f154ae70b1c5bfe8f9627a1355db710e63f648ef 100644 (file)
@@ -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"
index da1fd64e865517fad788232eb6df77c64c894ae0..2aa7838c93dcbbb038f0b1dda22f5c5e191795d5 100644 (file)
@@ -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
index 6603efe8d1f34d520bd1e15291c5c6e9761e892c..255c94a804961c1ec95b0f668270bd2579359561 100644 (file)
@@ -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.
index 1b16ebb9c635073b8ac6e4208fd69f40303b7431..80550f856dd71b5515ef18a4162953a072a884e3 100644 (file)
@@ -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)
index 0b190779b1aae9e5d9aa3e26939d2df8fd1f6f6e..efb283a1b8f659982503574c13233048e92c3f26 100644 (file)
@@ -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);
index 58198d0e1b988dfca31f10ad4f8466115e0465a9..46c3502f7778c6a8b2e93433df89dd6ec439fb38 100644 (file)
@@ -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.
index 27a44ad645fa7f3a7d44e1488c1cccbafb4c2ee6..dfda950bdd3ba5d41994a0d70266ec2a2e513957 100644 (file)
@@ -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)
+       }
+}