aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cmd/gc/builtin.c12
-rw-r--r--src/cmd/gc/esc.c19
-rw-r--r--src/cmd/gc/runtime.go12
-rw-r--r--src/cmd/gc/walk.c41
-rw-r--r--src/fmt/fmt_test.go9
-rw-r--r--src/runtime/string.go72
-rw-r--r--src/runtime/string_test.go11
7 files changed, 143 insertions, 33 deletions
diff --git a/src/cmd/gc/builtin.c b/src/cmd/gc/builtin.c
index e2e14f03d2..6682bfb66d 100644
--- a/src/cmd/gc/builtin.c
+++ b/src/cmd/gc/builtin.c
@@ -26,15 +26,15 @@ char *runtimeimport =
"func @\"\".printsp ()\n"
"func @\"\".printlock ()\n"
"func @\"\".printunlock ()\n"
- "func @\"\".concatstring2 (? string, ? string) (? string)\n"
- "func @\"\".concatstring3 (? string, ? string, ? string) (? string)\n"
- "func @\"\".concatstring4 (? string, ? string, ? string, ? string) (? string)\n"
- "func @\"\".concatstring5 (? string, ? string, ? string, ? string, ? string) (? string)\n"
- "func @\"\".concatstrings (? []string) (? string)\n"
+ "func @\"\".concatstring2 (? *[32]byte, ? string, ? string) (? string)\n"
+ "func @\"\".concatstring3 (? *[32]byte, ? string, ? string, ? string) (? string)\n"
+ "func @\"\".concatstring4 (? *[32]byte, ? string, ? string, ? string, ? string) (? string)\n"
+ "func @\"\".concatstring5 (? *[32]byte, ? string, ? string, ? string, ? string, ? string) (? string)\n"
+ "func @\"\".concatstrings (? *[32]byte, ? []string) (? string)\n"
"func @\"\".cmpstring (? string, ? string) (? int)\n"
"func @\"\".eqstring (? string, ? string) (? bool)\n"
"func @\"\".intstring (? int64) (? string)\n"
- "func @\"\".slicebytetostring (? []byte) (? string)\n"
+ "func @\"\".slicebytetostring (? *[32]byte, ? []byte) (? string)\n"
"func @\"\".slicebytetostringtmp (? []byte) (? string)\n"
"func @\"\".slicerunetostring (? []rune) (? string)\n"
"func @\"\".stringtoslicebyte (? string) (? []byte)\n"
diff --git a/src/cmd/gc/esc.c b/src/cmd/gc/esc.c
index 59b00bfa52..b636059749 100644
--- a/src/cmd/gc/esc.c
+++ b/src/cmd/gc/esc.c
@@ -694,6 +694,19 @@ esc(EscState *e, Node *n, Node *up)
e->noesc = list(e->noesc, n);
break;
+ case OARRAYBYTESTR:
+ n->escloopdepth = e->loopdepth;
+ n->esc = EscNone; // until proven otherwise
+ e->noesc = list(e->noesc, n);
+ break;
+
+ case OADDSTR:
+ n->escloopdepth = e->loopdepth;
+ n->esc = EscNone; // until proven otherwise
+ e->noesc = list(e->noesc, n);
+ // Arguments of OADDSTR do not escape.
+ break;
+
case OADDR:
n->esc = EscNone; // until proven otherwise
e->noesc = list(e->noesc, n);
@@ -806,6 +819,8 @@ escassign(EscState *e, Node *dst, Node *src)
case OMAKECHAN:
case OMAKEMAP:
case OMAKESLICE:
+ case OARRAYBYTESTR:
+ case OADDSTR:
case ONEW:
case OCLOSURE:
case OCALLPART:
@@ -837,6 +852,7 @@ escassign(EscState *e, Node *dst, Node *src)
case OSLICE3:
case OSLICEARR:
case OSLICE3ARR:
+ case OSLICESTR:
// Conversions, field access, slice all preserve the input value.
escassign(e, dst, src->left);
break;
@@ -1227,6 +1243,8 @@ escwalk(EscState *e, int level, Node *dst, Node *src)
case OMAKECHAN:
case OMAKEMAP:
case OMAKESLICE:
+ case OARRAYBYTESTR:
+ case OADDSTR:
case OMAPLIT:
case ONEW:
case OCLOSURE:
@@ -1243,6 +1261,7 @@ escwalk(EscState *e, int level, Node *dst, Node *src)
case OSLICEARR:
case OSLICE3:
case OSLICE3ARR:
+ case OSLICESTR:
escwalk(e, level, dst, src->left);
break;
diff --git a/src/cmd/gc/runtime.go b/src/cmd/gc/runtime.go
index 463bb3a76d..13b19ca5e2 100644
--- a/src/cmd/gc/runtime.go
+++ b/src/cmd/gc/runtime.go
@@ -39,16 +39,16 @@ func printsp()
func printlock()
func printunlock()
-func concatstring2(string, string) string
-func concatstring3(string, string, string) string
-func concatstring4(string, string, string, string) string
-func concatstring5(string, string, string, string, string) string
-func concatstrings([]string) string
+func concatstring2(*[32]byte, string, string) string
+func concatstring3(*[32]byte, string, string, string) string
+func concatstring4(*[32]byte, string, string, string, string) string
+func concatstring5(*[32]byte, string, string, string, string, string) string
+func concatstrings(*[32]byte, []string) string
func cmpstring(string, string) int
func eqstring(string, string) bool
func intstring(int64) string
-func slicebytetostring([]byte) string
+func slicebytetostring(*[32]byte, []byte) string
func slicebytetostringtmp([]byte) string
func slicerunetostring([]rune) string
func stringtoslicebyte(string) []byte
diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c
index b1622ae177..513aadf4ba 100644
--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -35,6 +35,12 @@ static int bounded(Node*, int64);
static Mpint mpzero;
static void walkprintfunc(Node**, NodeList**);
+// The constant is known to runtime.
+enum
+{
+ tmpstringbufsize = 32,
+};
+
void
walk(Node *fn)
{
@@ -1370,8 +1376,14 @@ walkexpr(Node **np, NodeList **init)
goto ret;
case OARRAYBYTESTR:
- // slicebytetostring([]byte) string;
- n = mkcall("slicebytetostring", n->type, init, n->left);
+ a = nodnil();
+ if(n->esc == EscNone) {
+ // Create temporary buffer for string on stack.
+ t = aindex(nodintconst(tmpstringbufsize), types[TUINT8]);
+ a = nod(OADDR, temp(t), N);
+ }
+ // slicebytetostring(*[32]byte, []byte) string;
+ n = mkcall("slicebytetostring", n->type, init, a, n->left);
goto ret;
case OARRAYBYTESTRTMP:
@@ -2720,9 +2732,10 @@ writebarrierfn(char *name, Type *l, Type *r)
static Node*
addstr(Node *n, NodeList **init)
{
- Node *r, *cat, *slice;
+ Node *r, *cat, *slice, *buf;
NodeList *args, *l;
int c;
+ vlong sz;
Type *t;
// orderexpr rewrote OADDSTR to have a list of strings.
@@ -2730,8 +2743,23 @@ addstr(Node *n, NodeList **init)
if(c < 2)
yyerror("addstr count %d too small", c);
+ buf = nodnil();
+ if(n->esc == EscNone) {
+ sz = 0;
+ for(l=n->list; l != nil; l=l->next) {
+ if(n->op == OLITERAL)
+ sz += n->val.u.sval->len;
+ }
+ // Don't allocate the buffer if the result won't fit.
+ if(sz < tmpstringbufsize) {
+ // Create temporary buffer for result string on stack.
+ t = aindex(nodintconst(tmpstringbufsize), types[TUINT8]);
+ buf = nod(OADDR, temp(t), N);
+ }
+ }
+
// build list of string arguments
- args = nil;
+ args = list1(buf);
for(l=n->list; l != nil; l=l->next)
args = list(args, conv(l->n, types[TSTRING]));
@@ -2747,9 +2775,10 @@ addstr(Node *n, NodeList **init)
t->bound = -1;
slice = nod(OCOMPLIT, N, typenod(t));
slice->alloc = n->alloc;
- slice->list = args;
+ slice->list = args->next; // skip buf arg
+ args = list1(buf);
+ args = list(args, slice);
slice->esc = EscNone;
- args = list1(slice);
}
cat = syslook(namebuf, 1);
r = nod(OCALL, cat, N);
diff --git a/src/fmt/fmt_test.go b/src/fmt/fmt_test.go
index c933e849fe..d7161c291d 100644
--- a/src/fmt/fmt_test.go
+++ b/src/fmt/fmt_test.go
@@ -869,6 +869,15 @@ func BenchmarkFprintInt(b *testing.B) {
}
}
+func BenchmarkFprintfBytes(b *testing.B) {
+ data := []byte(string("0123456789"))
+ var buf bytes.Buffer
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ Fprintf(&buf, "%s", data)
+ }
+}
+
func BenchmarkFprintIntNoAlloc(b *testing.B) {
var x interface{} = 123456
var buf bytes.Buffer
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)
+ }
+}