<ol dir="auto">
<li>What version of Go are you using (<code class="notranslate">go version</code>)?<br>
<code class="notranslate">go version go1.6 linux/amd64</code><br>
and<br>
<code class="notranslate">go version devel +ea4b785 Tue Mar 15 08:43:34 2016 +0000 linux/amd64</code></li>
<li>What operating system and processor architecture are you using (<code class="notranslate">go env</code>)?</li>
</ol>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="GOARCH=&quot;amd64&quot;
GOBIN=&quot;&quot;
GOEXE=&quot;&quot;
GOHOSTARCH=&quot;amd64&quot;
GOHOSTOS=&quot;linux&quot;
GOOS=&quot;linux&quot;
GOPATH=&quot;/home/justinn/Workspace/go&quot;
GORACE=&quot;&quot;
GOROOT=&quot;/usr/lib/go&quot;
GOTOOLDIR=&quot;/usr/lib/go/pkg/tool/linux_amd64&quot;
GO15VENDOREXPERIMENT=&quot;1&quot;
CC=&quot;gcc&quot;
GOGCCFLAGS=&quot;-fPIC -m64 -pthread -fmessage-length=0&quot;
CXX=&quot;g++&quot;
CGO_ENABLED=&quot;1&quot;"><pre class="notranslate"><code class="notranslate">GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH="/home/justinn/Workspace/go"
GORACE=""
GOROOT="/usr/lib/go"
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GO15VENDOREXPERIMENT="1"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0"
CXX="g++"
CGO_ENABLED="1"
</code></pre></div>
<ol dir="auto">
<li>What did you do?</li>
</ol>
<p dir="auto">Benchmarked (a function that calls) strings.Join and looked at the memory allocations with pprof. (With <code class="notranslate">go test -bench Join -benchmem -memprofile mem.pprof -memprofilerate 1 -run ^$</code>)</p>
<p dir="auto">Benchmarks for strings.Join: <a href="http://play.golang.org/p/R7snLsnFmg" rel="nofollow">http://play.golang.org/p/R7snLsnFmg</a></p>
<p dir="auto">Benchmark results:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="BenchmarkJoin0-8    500000000            3.21 ns/op        0 B/op          0 allocs/op
BenchmarkJoin1-8    500000000            3.66 ns/op        0 B/op          0 allocs/op
BenchmarkJoin2-8    20000000            80.8 ns/op         6 B/op          2 allocs/op"><pre class="notranslate"><code class="notranslate">BenchmarkJoin0-8    500000000            3.21 ns/op        0 B/op          0 allocs/op
BenchmarkJoin1-8    500000000            3.66 ns/op        0 B/op          0 allocs/op
BenchmarkJoin2-8    20000000            80.8 ns/op         6 B/op          2 allocs/op
</code></pre></div>
<p dir="auto">Memory profile for strings.Join (<code class="notranslate">go tool pprof --alloc_objects &lt;binary&gt; mem.pprof</code>):</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="   1204055    1204055 (flat, cum)   100% of Total
         .          .    358:   n := len(sep) * (len(a) - 1)
         .          .    359:   for i := 0; i &lt; len(a); i++ {
         .          .    360:       n += len(a[i])
         .          .    361:   }
         .          .    362:
    602028     602028    363:   b := make([]byte, n)
         .          .    364:   bp := copy(b, a[0])
         .          .    365:   for _, s := range a[1:] {
         .          .    366:       bp += copy(b[bp:], sep)
         .          .    367:       bp += copy(b[bp:], s)
         .          .    368:   }
    602027     602027    369:   return string(b)
         .          .    370:}"><pre class="notranslate"><code class="notranslate">   1204055    1204055 (flat, cum)   100% of Total
         .          .    358:   n := len(sep) * (len(a) - 1)
         .          .    359:   for i := 0; i &lt; len(a); i++ {
         .          .    360:       n += len(a[i])
         .          .    361:   }
         .          .    362:
    602028     602028    363:   b := make([]byte, n)
         .          .    364:   bp := copy(b, a[0])
         .          .    365:   for _, s := range a[1:] {
         .          .    366:       bp += copy(b[bp:], sep)
         .          .    367:       bp += copy(b[bp:], s)
         .          .    368:   }
    602027     602027    369:   return string(b)
         .          .    370:}
</code></pre></div>
<ol dir="auto">
<li>What did you expect to see?</li>
</ol>
<p dir="auto">The compiler reusing the memory of b for the return value.</p>
<ol dir="auto">
<li>What did you see instead?</li>
</ol>
<p dir="auto">The []byte-&gt;string conversion in strings.Join (on line 369) allocates a new string, although the []byte could be reused as there can never be a reference to the []byte after the function returns and the []byte is already on the heap¹.</p>
<p dir="auto">¹ Calling 'go build -gcflags="-m" in the strings package confirms this:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="./strings.go:363: make([]byte, n) escapes to heap
./strings.go:369: string(b) escapes to heap"><pre class="notranslate"><code class="notranslate">./strings.go:363: make([]byte, n) escapes to heap
./strings.go:369: string(b) escapes to heap
</code></pre></div>
<p dir="auto">(Line numbers are from tip)</p>
<pre class="notranslate">In this function,

func hi() string {
        b := make([]byte, 0, 100) // 1st alloc
        b = append(b, "Hello, "...)
        b = append(b, "world.\n"...)
        return string(b) // 2nd alloc, but could be removed.
}

The final line currently generates a call to runtime.slicebytetostring, causing a copy
of b to be allocated.

But the compiler already knows that b doesn't escape.  It could instead return a string
header re-using the []byte memory.</pre>