<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Garbage-Collection on Roberto Selbach</title><link>https://rselbach.com/tags/garbage-collection/</link><description>Recent content in Garbage-Collection on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Tue, 16 Dec 2014 17:40:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/garbage-collection/index.xml" rel="self" type="application/rss+xml"/><item><title>Go 1.4 moves the runtime toward Go</title><link>https://rselbach.com/go-1-4-moves-the-runtime-toward-go/</link><pubDate>Tue, 16 Dec 2014 17:40:00 +0000</pubDate><guid>https://rselbach.com/go-1-4-moves-the-runtime-toward-go/</guid><description>&lt;p&gt;I installed Go 1.4 to test a service and found the most interesting changes below my source code. Parts of the runtime and tools are being translated from C into Go. That work is not a promise that applications suddenly run faster; it is groundwork for making the implementation easier to evolve with one type system and toolchain.&lt;/p&gt;
&lt;p&gt;The collector is now fully precise. It identifies pointers in stacks and the heap rather than retaining objects because non-pointer data happens to resemble an address. Precision also requires cooperation from writes: write barriers record pointer updates that the collector must not miss while maintaining its view of the object graph. This is infrastructure for collector work, not the concurrent collector people keep casually attributing to this release. Go 1.4 still has stop-the-world collection behavior worth measuring.&lt;/p&gt;
&lt;p&gt;Goroutine stacks now start at 2 KiB, down from the larger starting size in previous releases, and grow by copying when needed. This materially reduces initial memory for programs with many mostly shallow goroutines. It does not make deep stacks free; growth allocates a larger contiguous stack and copies live contents using precise pointer information.&lt;/p&gt;
&lt;p&gt;Two source-level tools are immediately practical. &lt;code&gt;go generate&lt;/code&gt; lets a package record commands that generate source or other build inputs:&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:#75715e"&gt;//go:generate stringer -type=State&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running &lt;code&gt;go generate&lt;/code&gt; is explicit; &lt;code&gt;go build&lt;/code&gt; does not run generators automatically. I like that separation because builds should not unexpectedly require every generator or rewrite the tree. Generated files still need a clear policy in the repository, and the directive is a command for developers, not a dependency manager.&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;internal&lt;/code&gt; directory convention has an important 1.4-sized footnote. In this release, the restriction is enforced for packages in the main Go source repository, keeping implementation packages inside the standard library and toolchain. The intended rule is that code under &lt;code&gt;a/b/internal/x&lt;/code&gt; may be imported only by packages rooted within &lt;code&gt;a/b&lt;/code&gt;, but the &lt;code&gt;go&lt;/code&gt; command does not yet enforce that boundary for general repositories. Broader enforcement is future work.&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;example.com/project/internal/wire
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;example.com/project/server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can use the same layout in my own repository to advertise intent, but in Go 1.4 that part is still convention rather than a boundary enforced by the tool. It is worth adopting without pretending the lock is already on the door.&lt;/p&gt;
&lt;p&gt;After the upgrade I reran tests, allocation benchmarks, and latency measurements rather than assuming runtime changes were universally beneficial. The service used less memory with thousands of idle goroutines, while its GC tail latency remained visible. That is a believable improvement: smaller starting stacks and more precise tracing reduce waste, but write barriers and collection still have costs.&lt;/p&gt;
&lt;p&gt;Go 1.4 mostly removes future constraints. More runtime code in Go, complete pointer precision, and barriers prepare the implementation for deeper collector changes. Meanwhile, &lt;code&gt;go generate&lt;/code&gt; is useful now, and &lt;code&gt;internal&lt;/code&gt; shows where package boundaries are headed.&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>Chasing a Go latency spike</title><link>https://rselbach.com/chasing-a-go-latency-spike/</link><pubDate>Thu, 12 Jun 2014 19:05:00 +0000</pubDate><guid>https://rselbach.com/chasing-a-go-latency-spike/</guid><description>&lt;p&gt;An HTTP service of mine usually answered in a few milliseconds but occasionally took more than 100. Average latency looked healthy, which is one of the average&amp;rsquo;s less charming habits. I wanted to know whether requests were waiting on the network, the scheduler, or garbage collection.&lt;/p&gt;
&lt;p&gt;My first attempt added timers around every handler. That established that a slow request was slow, a breakthrough unlikely to threaten the scientific establishment. I needed observations below the handler.&lt;/p&gt;
&lt;p&gt;I started with &lt;code&gt;GODEBUG=gctrace=1&lt;/code&gt;. A run under load printed lines resembling:&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;gc1(1): 0+6+1 ms, 18 -&amp;gt; 22 MB 12033 -&amp;gt; 8411 (123004-114593) objects
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gc2(1): 0+9+1 ms, 25 -&amp;gt; 29 MB 15020 -&amp;gt; 9170 (171220-162050) objects
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The exact format is runtime-version dependent, but the useful correlation was clear: several latency spikes lined up with collections. The Go collector at this point includes stop-the-world work. During those pauses, goroutines do not make application progress. More processors do not turn a global pause into concurrent work.&lt;/p&gt;
&lt;p&gt;I then reduced allocation rate rather than immediately tuning the collector. A JSON path decoded first into &lt;code&gt;interface{}&lt;/code&gt; and then into a struct. Removing that intermediate object graph reduced collection frequency. Reusing a scratch byte slice within a request also helped, provided I kept ownership local and did not retain a huge backing array for a tiny value.&lt;/p&gt;
&lt;p&gt;Heap size and pause time are related but not identical. Setting &lt;code&gt;GOGC&lt;/code&gt; lower collects more often with a smaller heap; setting it higher collects less often while retaining more memory. Neither setting fixes accidental allocation. I leave &lt;code&gt;GOGC&lt;/code&gt; at its default until measurements show a reason to trade memory for collection frequency.&lt;/p&gt;
&lt;p&gt;The scheduler was the next suspect. Go multiplexes goroutines onto operating-system threads. Runnable goroutines wait in scheduler queues; goroutines blocked in syscalls or channel operations allow others to run. &lt;code&gt;GOMAXPROCS&lt;/code&gt; controls how many processors may execute Go code simultaneously, not how many sockets the program may hold and not a magic goroutine limit.&lt;/p&gt;
&lt;p&gt;I had one CPU-heavy goroutine parsing a large report. Function calls provide scheduling opportunities in the current runtime, but preemption is only partial. A long computation with few safe points can delay other runnable work, particularly with a low &lt;code&gt;GOMAXPROCS&lt;/code&gt;. Breaking the report into bounded jobs improved fairness and made cancellation by closing a work channel practical. Adding &lt;code&gt;runtime.Gosched&lt;/code&gt; inside the innermost loop helped one test and made the algorithm uglier, so I did not keep it.&lt;/p&gt;
&lt;p&gt;Network waiting has a different path. On supported systems, the runtime&amp;rsquo;s network poller integrates non-blocking descriptors with mechanisms such as kqueue or epoll. A goroutine waiting for socket readiness can park without consuming an operating-system thread for the duration. When the descriptor becomes ready, the poller makes the goroutine runnable again.&lt;/p&gt;
&lt;p&gt;This is why one goroutine per connection can be practical. It does not mean each connection has a dedicated thread. It also does not mean network code is immune to thread exhaustion: file operations and syscalls that the poller cannot manage may block threads, DNS behavior varies by platform and resolver path, and cgo calls have their own costs.&lt;/p&gt;
&lt;p&gt;I made the wait visible by taking a goroutine profile from &lt;code&gt;net/http/pprof&lt;/code&gt; while the service was loaded. Most goroutines were parked in network reads, which was normal. The interesting stack was a growing set waiting on one mutex around a response cache. My &amp;ldquo;thread-safe map&amp;rdquo; protected JSON encoding under the lock, turning a short critical section into serialized CPU work.&lt;/p&gt;
&lt;p&gt;The repair copied the cached value&amp;rsquo;s reference while holding the lock, unlocked, and encoded afterward. That shortened scheduler queues during bursts. I verified the data was immutable before doing this; moving mutable state outside a lock would have exchanged latency for corruption, a poor bargain even by my standards.&lt;/p&gt;
&lt;p&gt;One test run then looked roughly 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-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;before: p50=4ms p95=37ms p99=128ms
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;after: p50=3ms p95=11ms p99=42ms
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The remaining tail still correlated with collections. This was not a proof that all pauses were GC pauses: kernel scheduling, packet loss, disk activity, and clients contribute too. I kept wall-clock request histograms beside allocation and GC observations rather than assigning every gap to the runtime.&lt;/p&gt;
&lt;p&gt;The mechanism suggests a practical diagnostic order. First, establish whether time is spent runnable, blocked on synchronization, parked on network I/O, or stopped globally. Goroutine profiles reveal blocking locations, mutex and channel structure reveal serialization, GC traces reveal collection timing, and CPU profiles reveal actual execution. Then change the source of delay.&lt;/p&gt;
&lt;p&gt;I also capped the work queue. Before that change, a burst created more live request state than workers could consume, raising heap size and GC cost after traffic subsided. Back pressure made overload visible to callers instead of translating it into a delayed collector problem. A goroutine waiting in a queue is still retained work.&lt;/p&gt;
&lt;p&gt;My direct opinion is that &amp;ldquo;goroutines are cheap&amp;rdquo; is useful sales material and terrible capacity planning. Their stacks consume memory, their work must be scheduled, their objects burden the collector, and their shared locks serialize. The runtime makes concurrency remarkably approachable, but it cannot make an unbounded queue or a giant critical section reasonable. Mine was a runtime mystery only until I measured my own code.&lt;/p&gt;</description></item><item><title>Measuring and tuning Go GC pauses</title><link>https://rselbach.com/measuring-and-tuning-go-gc-pauses/</link><pubDate>Fri, 28 Dec 2012 14:36:00 +0000</pubDate><guid>https://rselbach.com/measuring-and-tuning-go-gc-pauses/</guid><description>&lt;p&gt;One request in a test server occasionally took much longer than its neighbours. Rather than convict the collector from one latency graph, I recorded request times and the runtime&amp;rsquo;s pause samples on the same workload.&lt;/p&gt;
&lt;p&gt;The current collector stops application work while it traces the reachable heap. &lt;code&gt;runtime.ReadMemStats&lt;/code&gt; exposes &lt;code&gt;NumGC&lt;/code&gt;, &lt;code&gt;PauseTotalNs&lt;/code&gt;, and a ring of recent &lt;code&gt;PauseNs&lt;/code&gt; values. I sampled before and after a fixed five-minute run, then compared only collections added during that window. Mixing in old pauses made short tests look wonderfully stable or mysteriously awful depending on what had run first.&lt;/p&gt;
&lt;p&gt;That pause is easy to describe and harder to judge. A short command-line tool may never care. A busy service with latency requirements can care very much, even if its average throughput looks fine. Averages are talented at concealing the request somebody was actually waiting for.&lt;/p&gt;
&lt;p&gt;My first table kept four numbers for each run: requests per second, the 50th and 99th percentile request latency, collection count, and the largest observed GC pause. Average latency alone hid the event I cared about.&lt;/p&gt;
&lt;p&gt;The naive handler allocated a buffer for every item:&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;checksum&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;items&lt;/span&gt; [][]&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;uint32&lt;/span&gt; {
&lt;/span&gt;&lt;/span&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;sum&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint32&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;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;item&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;items&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make([]&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;, len(&lt;span style="color:#a6e22e"&gt;item&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		copy(&lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;item&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;_&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:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;buf&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;			&lt;span style="color:#a6e22e"&gt;sum&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; uint32(&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&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;sum&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 served no purpose. Removing it reduced collections and improved the long tail. This was not clever tuning; I had simply stopped manufacturing garbage.&lt;/p&gt;
&lt;p&gt;The collector&amp;rsquo;s job depends on both allocated volume and live data. Producing temporary objects quickly causes collections to happen more often. Retaining a large graph gives each marking pass more reachable memory to inspect. These are different problems: one concerns allocation rate, the other heap size and object reachability.&lt;/p&gt;
&lt;p&gt;After that fix I tried &lt;code&gt;GOGC=50&lt;/code&gt;, the default &lt;code&gt;100&lt;/code&gt;, and &lt;code&gt;200&lt;/code&gt;, restarting the process for each run. Lowering it collected more often with a smaller heap. Raising it collected less often but used more memory. On this service, &lt;code&gt;200&lt;/code&gt; helped the tail a little and increased memory enough that I kept the default. That is a result, not a disappointment.&lt;/p&gt;
&lt;p&gt;Manual &lt;code&gt;runtime.GC()&lt;/code&gt; calls were useful to prove that the latency recorder could see a pause. They did not belong in the handler. Forcing collection chooses when to pay and can easily turn an occasional pause into a dependable stream of them.&lt;/p&gt;
&lt;p&gt;The useful knob came last. First came a repeatable load, pause deltas from the same time window, and removal of an accidental allocation. Otherwise &lt;code&gt;GOGC&lt;/code&gt; is just a dial with excellent opportunities for superstition.&lt;/p&gt;</description></item><item><title>Go 1 stop-the-world garbage collection</title><link>https://rselbach.com/go-1-stop-the-world-garbage-collection/</link><pubDate>Wed, 01 Aug 2012 20:20:00 +0000</pubDate><guid>https://rselbach.com/go-1-stop-the-world-garbage-collection/</guid><description>&lt;p&gt;I graphed request times for a Go 1 service and found narrow spikes hiding behind a good average. CPU was not saturated and the network was quiet. Allocation tracing pointed toward the garbage collector.&lt;/p&gt;
&lt;p&gt;The reduced workload simply creates and drops temporary objects:&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;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;line&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;lines&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fields&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;Split&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;line&lt;/span&gt;, []byte(&lt;span style="color:#e6db74"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;consume&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;fields&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;In Go 1, garbage collection stops ordinary Go execution while the collector identifies reachable heap objects and reclaims unreachable storage. The runtime coordinates goroutines at safe points, considers roots including stacks and global data, traces pointers through live objects, and sweeps storage that is no longer reachable. Those pauses are a direct property of the collector in today&amp;rsquo;s release.&lt;/p&gt;
&lt;p&gt;The surprise was the difference between throughput and latency. The total collection work can be a tolerable fraction of a ten-second run while one request arriving during a stop sees the pause directly. An average conceals where time is concentrated. Percentiles and a timeline expose it.&lt;/p&gt;
&lt;p&gt;Live heap size and allocation rate both matter, but differently. More live objects give the marker more reachable data to examine. A high rate of short-lived allocation fills available heap space quickly and brings the next collection nearer. Retaining a tiny pointer into a large structure can keep the structure reachable even when the programme considers most of it finished.&lt;/p&gt;
&lt;p&gt;The collector does not compact the heap into one tidy block. That avoids rewriting every pointer but leaves allocation and free-space management to the runtime. Details of size classes, metadata, and precisely how particular regions are scanned are implementation-specific. The safe source-level rule is reachability, not a guessed address or object movement policy.&lt;/p&gt;
&lt;p&gt;I prefer reducing obvious temporary allocation at a clear boundary. In this parser, reusing a scratch slice and avoiding repeated separator construction improved the latency graph without turning the package into an object pool. A profile before and after keeps that change honest.&lt;/p&gt;
&lt;p&gt;The caveat is ownership. Reusing a buffer while another goroutine still reads it trades collection pauses for data races and corrupted requests, an impressively bad bargain. Pools can also retain far more memory than expected. Simpler allocation remains the right default until measurement says otherwise.&lt;/p&gt;
&lt;p&gt;Go&amp;rsquo;s collector will certainly evolve, but application explanations should not arrive from the future. For Go 1 today, stop-the-world pauses are part of the runtime&amp;rsquo;s cost model. My service became steadier after allocating less, and the graph became considerably less interesting. Production graphs are at their best when they are poor conversationalists.&lt;/p&gt;</description></item><item><title>Watching the Go garbage collector stop</title><link>https://rselbach.com/watching-the-go-garbage-collector-stop/</link><pubDate>Wed, 16 Nov 2011 19:40:00 +0000</pubDate><guid>https://rselbach.com/watching-the-go-garbage-collector-stop/</guid><description>&lt;p&gt;I noticed a small server pausing at intervals under an allocation-heavy test. The average request time looked respectable, but individual requests occasionally took a visible detour. That pattern made the old garbage collector worth examining.&lt;/p&gt;
&lt;p&gt;I reduced the trigger to a loop that replaces short-lived buffers:&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;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &amp;lt; &lt;span style="color:#a6e22e"&gt;n&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&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:#a6e22e"&gt;b&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make([]&lt;span style="color:#66d9ef"&gt;byte&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;&lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;consume&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first surprise was that allocation speed and collection latency are separate observations. Allocation can be cheap while creating enough work for periodic collections. An average over the entire run smooths those pauses into a harmless-looking number.&lt;/p&gt;
&lt;p&gt;The collector in this runtime is a stop-the-world collector. When a collection begins, Go execution is brought to a safe point and paused while the runtime determines which heap objects are reachable. It starts from roots such as stacks and global data, follows pointers through reachable objects, and eventually reclaims storage that was not found. Application goroutines do not continue doing ordinary work during that collection.&lt;/p&gt;
&lt;p&gt;This explains why adding processors does not simply make the pause disappear. It may improve the application between collections, but the collector&amp;rsquo;s coordination and tracing remain a shared event. It also explains why retaining one pointer to a large structure matters: reachability, not whether I personally remember using the object, determines whether it survives.&lt;/p&gt;
&lt;p&gt;I prefer reducing needless allocation only after a profile or latency measurement identifies it. Reusing a buffer at a clear ownership boundary can help. Converting every local variable into a global cache can help the heap grow mysterious hair. Smaller live data and fewer transient objects are good outcomes; cleverness measured only by line count is not.&lt;/p&gt;
&lt;p&gt;The caveat is that stack and heap placement are compiler decisions, and the compiler is changing. I avoid writing code that depends on a guessed escape decision. I also avoid claims about a future collector when explaining this one. Today&amp;rsquo;s pauses are real even if the runtime will improve.&lt;/p&gt;
&lt;p&gt;For the server, pooling two large scratch buffers around a serialized operation removed most of the allocation burst. I kept the simpler allocating version in a benchmark so the trade remains measurable. The garbage collector was doing exactly what it had been asked to do; I had simply been sending rather enthusiastic invitations.&lt;/p&gt;</description></item></channel></rss>