Why does raising GOGC from 100 to 400 cut GC CPU time but raise a Go service's peak memory?
answer
- marking cost follows live data, not garbage
- the knob spaces cycles out
- CPU falls like one over GOGC
- memory rises like one plus GOGC over 100
- the last doublings buy almost nothing
basics
~20 sA collection cycle's cost tracks live data, not garbage, so quadrupling the growth allowance runs about a quarter as many cycles for the same allocation rate. The price is the heap the process holds between cycles, which grows roughly fourfold.
solid answer
~50 sThe work in one cycle is dominated by marking reachable objects, and `GOGC` does not change how much data is reachable — so each cycle costs about the same no matter what `GOGC` is. What `GOGC` changes is how much a program may allocate between cycles: at 400 it may allocate four times as much as at 100 before the next one starts, so for a fixed allocation rate roughly a quarter as many cycles run and total GC CPU drops by about the same factor. The memory bill is the mirror image: the heap is allowed to reach about five times the live set instead of twice, so peak resident memory rises accordingly. The returns diminish fast — 100 to 200 removes half the GC CPU, 800 to 1600 removes a few percent for another doubling of memory.
go deeper
Know the direction: higher GOGC means fewer collections and more memory, lower means more collections and less memory. Being able to state the tradeoff cleanly is enough at this level.
Explain the mechanism — marking cost tracks live data while GOGC controls the allocation between cycles — and do the arithmetic for a concrete live-set size in both directions.
An interviewer expects the diminishing-returns curve and the second-order costs: mark assists appearing in request latency at low values, and worse locality plus a larger blast radius at high ones.
Frame the knob as a measured exchange rate between two billed resources and insist the value be chosen at the knee of a real load test, with the peak-heap number written down and revisited when the live set grows.
## Two quantities move in opposite directions `GOGC` is a percentage of the live set that a Go program may allocate on top of that live set before the next collection cycle begins. Raising it from 100 to 400 changes two things, and understanding *why* each moves requires knowing what a cycle actually costs. ### What one cycle costs The expensive phase is **marking**: walking from the roots (stacks, globals) through every pointer to find what is still reachable. That work is proportional to the amount of **live, pointer-carrying data** — not to the amount of garbage. Garbage is, in a sweeping collector, close to free: unmarked memory is simply reusable, and sweeping is incremental and cheap. So: *the cost of one cycle depends on the live set, which `GOGC` does not change.* ### What GOGC changes `GOGC` changes the **spacing** between cycles. At `GOGC=100`, a program with a 200 MB live set may allocate about 200 MB before the next cycle. At `GOGC=400`, it may allocate about 800 MB. For a service allocating at a steady rate: ``` cycles per gigabyte allocated ~ 1 / (GOGC/100) total GC CPU per gigabyte ~ (cost of one cycle) / (GOGC/100) ``` Quadruple the allowance, run a quarter as many cycles, pay roughly a quarter of the GC CPU. The knob is, to a first approximation, an inverse-proportional CPU dial. ### And the memory side Peak heap moves the other way, linearly: ``` peak heap ~ live set * (1 + GOGC/100) ``` - `GOGC=100`: about 2x the live set. - `GOGC=400`: about 5x the live set. So the trade at 100 -> 400 is roughly "2.5x the memory for a quarter of the GC CPU". ### Diminishing returns — the part that decides real tuning Because CPU falls as `1/GOGC` while memory rises as `1+GOGC/100`, the deal gets steadily worse: | GOGC | approx. peak heap | approx. GC CPU vs default | |---|---|---| | 50 | 1.5x live | ~2x | | 100 | 2x live | 1x (baseline) | | 200 | 3x live | ~0.5x | | 400 | 5x live | ~0.25x | | 800 | 9x live | ~0.12x | | 1600 | 17x live | ~0.06x | Going 100 -> 200 removes half the GC CPU for one extra copy of the live set. Going 800 -> 1600 removes about six percent of the original GC CPU for eight more copies. **Almost all the win is in the first few doublings**, which is why tuning past a few hundred rarely pays and why the curve has an obvious knee you should find by measurement rather than by argument. ### The costs that are not on the arithmetic Raising `GOGC` is not free beyond the byte count: - **More floating garbage** sits in the heap for longer, so the working set spans more pages and cache and TLB behaviour can get worse — occasionally enough to erase part of the CPU win. - **A bigger heap means a bigger blast radius** when the live set itself grows; the multiplier applies to the new, larger live set too. Lowering `GOGC` has its own non-obvious costs: - Cycles start close together, and when allocation outruns the collector the allocating goroutine is charged **mark assist** work, so the latency shows up inside your request path rather than in a background worker. - Below a certain point you pay a great deal of CPU for very little memory, because you are only squeezing headroom — the live set is untouched. ### The sentence that answers the question "Cycle cost follows live data; `GOGC` follows allocation between cycles. Raising it spaces the cycles out, so you pay for fewer of them and hold more garbage while you wait."
- Why does raising GOGC from 800 to 1600 disappoint a team that got a big win going from 100 to 200?GC CPU falls in proportion to 1/GOGC, so by 800 there is only about an eighth of the original GC cost left to remove; doubling again claws back a few percent of the original while doubling the allowed heap again. The memory cost is linear and the CPU saving is hyperbolic, so the deal degrades with every step.
- Does a higher GOGC make individual collection cycles longer?Not materially. Mark work tracks the live set, which GOGC does not change, so one cycle costs roughly the same at 100 or at 400 — there are simply fewer of them. What does grow is how much garbage sits resident between cycles, which is a memory effect rather than a per-cycle time effect.
- If a service is already spending very little CPU on collection, is there any reason to raise GOGC?Usually no. The knob only buys back GC CPU, so when GC CPU is already a rounding error there is nothing to win, and the memory increase is real. Measure GC's share of CPU first; if it is a couple of percent, spend the tuning effort somewhere the profile actually points.
Emptying the bins less often does not make each trip harder — the trip's cost is the walk, not the rubbish. You just live with fuller bins in between.
saying these in an interview costs you the question
- Claims a higher GOGC makes each collection cycle proportionally longer
- Thinks the collector's cost scales with the amount of garbage
- Expects the same CPU saving from every doubling of GOGC
- Says lowering GOGC shrinks the live set
- Treats raising GOGC as free because the container has spare RAM