<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Go1.3 on Roberto Selbach</title><link>https://rselbach.com/tags/go1.3/</link><description>Recent content in Go1.3 on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 09 Oct 2014 13:10:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/go1.3/index.xml" rel="self" type="application/rss+xml"/><item><title>Parallel benchmarks and wandering maps</title><link>https://rselbach.com/parallel-benchmarks-and-wandering-maps/</link><pubDate>Thu, 09 Oct 2014 13:10:00 +0000</pubDate><guid>https://rselbach.com/parallel-benchmarks-and-wandering-maps/</guid><description>&lt;p&gt;I tested a locked lookup table with Go 1.3&amp;rsquo;s parallel benchmark support:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BenchmarkLookupParallel&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;testing&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;B&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;RunParallel&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pb&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;testing&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;PB&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pb&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Next&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;table&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Lookup&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;troy&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;RunParallel&lt;/code&gt; creates goroutines and distributes benchmark iterations among them. This exposed lock contention that a serial benchmark politely concealed. It is still synthetic: all workers hammering one key may be less realistic than production, so I use representative key distributions too.&lt;/p&gt;
&lt;p&gt;The same upgrade broke a test that compared map traversal output to a fixed string. Go 1.3 randomizes iteration order for small maps, making an assumption that was always invalid fail more reliably.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;k&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;m&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;keys&lt;/span&gt; = append(&lt;span style="color:#a6e22e"&gt;keys&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;k&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;sort&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Strings&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;keys&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sorting is correct when output order is part of the result. For equality, I compare contents instead of rendering iteration order.&lt;/p&gt;
&lt;p&gt;Random order is not cryptographic randomness, nor should a program depend on getting a different order every pass. It is an implementation defense against accidental ordering assumptions, not a shuffling API.&lt;/p&gt;
&lt;p&gt;I approve of the randomization. A runtime that accidentally rewards a bad assumption lets the assumption become an API. My test was not flaky; it was finally honest.&lt;/p&gt;</description></item><item><title>sync.Pool is not a cache</title><link>https://rselbach.com/sync-pool-is-not-a-cache/</link><pubDate>Thu, 18 Sep 2014 16:25:00 +0000</pubDate><guid>https://rselbach.com/sync-pool-is-not-a-cache/</guid><description>&lt;p&gt;I used Go 1.3&amp;rsquo;s new &lt;code&gt;sync.Pool&lt;/code&gt; to reuse temporary buffers in an HTTP formatter. Then I almost used it to cache parsed templates. The first idea reduced garbage; the second would have randomly forgotten application data.&lt;/p&gt;
&lt;p&gt;A pool holds temporary values that may be removed at any time. Garbage collection is explicitly allowed to clear it. That makes it suitable for amortizing allocation of scratch objects, not for storing anything whose presence affects correctness.&lt;/p&gt;
&lt;p&gt;My buffer pool looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;buffers&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;sync&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Pool&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;New&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;func&lt;/span&gt;() &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt;{} {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; new(&lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Buffer&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	},
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;render&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;v&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;) []&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;buffers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;().(&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Buffer&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Reset&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;writeValue&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;v&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;out&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; append([]byte(&lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;), &lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Bytes&lt;/span&gt;()&lt;span style="color:#f92672"&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Len&lt;/span&gt;() &amp;lt; &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Reset&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;buffers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Put&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;out&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The copy before &lt;code&gt;Put&lt;/code&gt; is essential. &lt;code&gt;b.Bytes()&lt;/code&gt; aliases the buffer&amp;rsquo;s storage. Returning that slice and putting the buffer back would allow another goroutine to overwrite the caller&amp;rsquo;s result. Pool safety does not transfer ownership safety to pooled objects.&lt;/p&gt;
&lt;p&gt;The pool synchronizes &lt;code&gt;Get&lt;/code&gt; and &lt;code&gt;Put&lt;/code&gt;, but an object obtained from it belongs to the caller until returned. Two goroutines must not use the same buffer concurrently. Go 1.3&amp;rsquo;s &lt;code&gt;bytes.Buffer&lt;/code&gt; does not expose its capacity, so I use the rendered length as a simple retention policy: a response that grows a buffer past 64 KiB does not return that buffer to the pool. It is only a proxy for capacity, but it keeps a freshly oversized allocation out of the pool.&lt;/p&gt;
&lt;p&gt;In a benchmark of the formatter, pooling reduced allocation volume:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BenchmarkRender-4 500000 4100 ns/op 2304 B/op 12 allocs/op
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BenchmarkRenderPool-4 500000 3000 ns/op 768 B/op 5 allocs/op
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The output copy accounts for necessary ownership. If the function wrote directly to an &lt;code&gt;io.Writer&lt;/code&gt;, even that copy could disappear, probably a better API than ever more pooling.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;New&lt;/code&gt; should return the expected type consistently. A pool may omit &lt;code&gt;New&lt;/code&gt;, in which case &lt;code&gt;Get&lt;/code&gt; can return nil and every caller must handle that. I define it when all callers need the same scratch type; this keeps allocation policy together without pretending values are permanent.&lt;/p&gt;
&lt;p&gt;Copying a &lt;code&gt;sync.Pool&lt;/code&gt; after first use is invalid because its synchronization state and local storage are not value-like application data. I keep pools as package variables or pointer-owned fields initialized once.&lt;/p&gt;
&lt;p&gt;I would not add &lt;code&gt;sync.Pool&lt;/code&gt; without allocation profiles and a benchmark. It obscures object lifetime and may provide little benefit when allocations are already short and cheap. A cache promises retrieval; a pool merely offers to rummage in a box the collector is free to empty.&lt;/p&gt;</description></item><item><title>The Go 1.3 linker got less clever</title><link>https://rselbach.com/the-go-1-3-linker-got-less-clever/</link><pubDate>Thu, 28 Aug 2014 14:05:00 +0000</pubDate><guid>https://rselbach.com/the-go-1-3-linker-got-less-clever/</guid><description>&lt;p&gt;One of our larger binaries spent enough time linking that I could switch windows, forget why, and begin an unrelated task. Go 1.3&amp;rsquo;s linker refactor made that interruption noticeably shorter.&lt;/p&gt;
&lt;p&gt;The old linker represented symbols and their data using structures that encouraged many small allocations and expensive traversals. The 1.3 work reorganized that machinery around more compact symbol data and a cleaner division of the linking stages. The user-facing command did not change:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ go build ./cmd/server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The wait did. On one clean build of our program I saw roughly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Go 1.2.2 real 4.8s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Go 1.3 real 3.1s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is not a controlled toolchain benchmark. Installed package archives, the filesystem, machine load, and exact source all affect it. Repeating alternating builds from the same installed state still showed enough difference that it was not merely optimism generated by a new version number.&lt;/p&gt;
&lt;p&gt;Linking a Go program is substantial work. The linker reads object data for the program and imported packages, resolves symbol references, applies relocations, lays out executable sections, emits metadata used by reflection and the runtime, and writes the target executable. It also removes unreachable code and data. A statically linked Go binary may contain a great deal of runtime and type information, so representation overhead inside the linker matters.&lt;/p&gt;
&lt;p&gt;The refactor is interesting because it improves a tool by making its internal model less elaborate. No language feature was needed. Existing builds became faster and used less memory because the implementation stopped turning every byte and symbol into quite so much bookkeeping.&lt;/p&gt;
&lt;p&gt;I still avoid reading too much into final binary size. Importing a package can pull in initialization and reachable symbols, reflection can retain type metadata, and debug information contributes too. A smaller linker heap is not the same thing as a smaller output file.&lt;/p&gt;
&lt;p&gt;Cross-compilation makes architecture-specific tool names visible in this toolchain. The compiler and linker remain separate programs selected for the target architecture, even though &lt;code&gt;go build&lt;/code&gt; normally hides the sequence. When investigating a failure I use &lt;code&gt;go build -x&lt;/code&gt; to print invoked commands rather than reconstructing them from folklore.&lt;/p&gt;
&lt;p&gt;That output also shows whether the build uses installed archives from &lt;code&gt;GOROOT/pkg&lt;/code&gt; and &lt;code&gt;GOPATH/pkg&lt;/code&gt; or recompiles packages. Without it, I had occasionally blamed the linker for time actually spent rebuilding a dependency after a build-tag change.&lt;/p&gt;
&lt;p&gt;Benchmark builds with and without the relevant installed archives separately. Archives installed for the standard library under &lt;code&gt;GOROOT/pkg&lt;/code&gt; and for workspace packages under &lt;code&gt;GOPATH/pkg&lt;/code&gt; can hide compilation work; release builders often expose linker time more clearly. Go 1.3 improved both my build and my attention span, though only one claim has numbers behind it.&lt;/p&gt;</description></item><item><title>What Go 1.3 did to GC pauses</title><link>https://rselbach.com/what-go-1-3-did-to-gc-pauses/</link><pubDate>Tue, 15 Jul 2014 13:20:00 +0000</pubDate><guid>https://rselbach.com/what-go-1-3-did-to-gc-pauses/</guid><description>&lt;p&gt;I upgraded a memory-heavy worker to Go 1.3 expecting the garbage collector to become invisible. It did not. What changed was more specific and more useful than my expectation.&lt;/p&gt;
&lt;p&gt;Go 1.3 performs stack scanning precisely. The collector knows which stack slots contain pointers instead of treating pointer-shaped integers as possible references. This avoids keeping some dead heap objects alive accidentally and enables the runtime to copy growing stacks while correcting pointers.&lt;/p&gt;
&lt;p&gt;The release also adds concurrent sweeping. After marking determines which heap objects remain reachable, reclaiming unmarked spans can overlap with application execution rather than keeping all sweep work inside one stop-the-world interval. Allocation may assist or encounter sweep work later, so the cost has moved and changed shape rather than vanished.&lt;/p&gt;
&lt;p&gt;I compared GC traces from the same batch input:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Go 1.2: collections less regular, several pauses near 20ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Go 1.3: shorter observed pauses, sweep work visible during execution
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Those are observations from one program, not a portable benchmark. Heap layout, processor count, allocation pattern, and runtime revisions all matter. The collector still stops the world for portions of collection. Go 1.3 is not the future fully concurrent collector some discussions seem to have installed by enthusiasm.&lt;/p&gt;
&lt;p&gt;One accidental retention did disappear after the upgrade because a pointer-looking value on a stack no longer kept an object alive. I did not count that as permission to ignore memory profiles. The worker also retained a 16 MB byte slice through a 40-byte subslice, and precise scanning quite correctly kept the whole backing array reachable.&lt;/p&gt;
&lt;p&gt;The fix for that case remained a copy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; append([]byte(&lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt;), &lt;span style="color:#a6e22e"&gt;largeBuffer&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;:&lt;span style="color:#a6e22e"&gt;end&lt;/span&gt;]&lt;span style="color:#f92672"&gt;...&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copying 40 bytes allowed the large buffer to die. Runtime precision can distinguish pointers from integers; it cannot infer that I only care about a tiny section of a live array.&lt;/p&gt;
&lt;p&gt;Marking begins from roots such as globals and goroutine stacks and follows pointers through reachable heap objects. Precise stack maps improve the root set, while concurrent sweeping deals with memory already proven dead. They improve different phases; calling both simply &amp;ldquo;the new GC&amp;rdquo; hides why a workload changes.&lt;/p&gt;
&lt;p&gt;I keep pause percentiles and total CPU in view. Moving sweep work out of a pause can reduce tail latency while leaving processor work to be paid during ordinary execution. That is usually a good trade for my service, but a batch program may value throughput differently.&lt;/p&gt;
&lt;p&gt;My practical conclusion is to upgrade for the collector improvements, then measure again. Lower pauses do not make allocation free, concurrent sweep does not eliminate all latency, and a precise collector will faithfully preserve every object my program still references, including the embarrassingly large ones.&lt;/p&gt;</description></item><item><title>Growing stacks without segments</title><link>https://rselbach.com/growing-stacks-without-segments/</link><pubDate>Tue, 24 Jun 2014 15:45:00 +0000</pubDate><guid>https://rselbach.com/growing-stacks-without-segments/</guid><description>&lt;p&gt;After installing Go 1.3 I reran a recursive parser benchmark that had always behaved oddly at certain depths. The new runtime changed how goroutine stacks grow, and the result finally matched the shape I expected.&lt;/p&gt;
&lt;p&gt;Before 1.3, a goroutine stack could grow by adding another segment. Function entry checked available space and called runtime machinery when a split was needed. Repeatedly crossing a boundary in a call pattern could produce the notorious &amp;ldquo;hot stack split&amp;rdquo;: allocate a segment, return and release it, then need it again on the next iteration.&lt;/p&gt;
&lt;p&gt;Go 1.3 replaces segmented growth with a contiguous stack that is copied to a larger allocation. Pointers into the old stack must be adjusted to point into the copy. Stack addresses therefore are not permanent, which is one reason converting a Go pointer to an integer and treating it as a stable address is a rotten idea.&lt;/p&gt;
&lt;p&gt;My synthetic test recursively visited a nested expression:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;depth&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt; &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;depth&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;child&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The benchmark result on my machine was illustrative rather than universal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Go 1.2: 18400 ns/op
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Go 1.3: 12100 ns/op
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Normal request depths showed little difference. The improvement appeared near stack-growth behavior, exactly where a microbenchmark can exaggerate it. I am not claiming every recursive function became one third faster.&lt;/p&gt;
&lt;p&gt;The copying design depends on knowing which stack words are pointers. Go 1.3 has precise stack scanning, allowing the runtime to update real pointers rather than conservatively treating any pointer-looking word as one. This also helps garbage collection avoid retaining objects merely because an integer happens to resemble an address.&lt;/p&gt;
&lt;p&gt;Contiguous does not mean fixed. A goroutine begins with a modest stack and grows when a function&amp;rsquo;s stack check says more room is required. Growth now entails allocation and copying, so unexpectedly enormous stacks still cost memory and time. Deep recursion can still fail; it merely has a better growth mechanism on the way there.&lt;/p&gt;
&lt;p&gt;Stack copying happens at safe points where the runtime understands frame layout. Code that passes Go pointers through unsafe integer storage defeats assumptions needed to relocate them. &lt;code&gt;unsafe&lt;/code&gt; was never a promise that undocumented runtime addresses would survive stack growth; Go 1.3 merely makes that bad bargain fail more creatively.&lt;/p&gt;
&lt;p&gt;I removed a manual recursion limit that existed solely to dodge segmented-stack performance, but kept the limit that rejects maliciously deep input. A runtime improvement is not an input-validation policy. It is, however, a welcome deletion of one workaround.&lt;/p&gt;</description></item></channel></rss>