AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Looking back at the week of 25 February–3 March 2026

A slice allocation is not always a heap allocation

Go 1.25 and 1.26 changed slice backing-store placement. Judge lifetime, escape behavior and real workloads before adding manual capacity bookkeeping.

Citiți articolul în română

Counting allocation expressions in Go source is a poor substitute for understanding the lifetime of the data they create. A slice needs storage for its elements, but the compiler may place that backing store on the stack. Fewer visible allocation sites do not automatically mean less heap work. Before adding manual capacity estimates, establish where the data lives, whether it escapes and what the real workload requires.

The week beginning 25 February 2026 brought Keith Randall's 27 February explanation of stack allocation. It described distinct improvements across Go 1.25 and Go 1.26. The latter had already been released on 10 February. The explanation's date therefore marks a discussion of the implementation, rather than a new release of the language on 27 February.

The storage outlives an expression

A slice's description and the storage holding its elements are different things to reason about. An append operation may reuse existing capacity or need more storage. Merely counting occurrences of append says little about how often growth occurs during a particular execution. Likewise, a make expression does not by itself establish that its backing store must be allocated on the heap.

Lifetime supplies the more useful question. Storage that is needed only within a function's active work may be eligible for stack placement. Storage that must remain valid afterwards needs a representation that survives that frame. Escape analysis helps the compiler determine which situations it can safely optimize. The source-level spelling is part of the input to that reasoning, rather than an exact bill for runtime allocation.

Consider a hypothetical function that collects a small batch and processes it before returning. Compare it with a function that returns the collected batch to its caller. Both may grow a slice in a loop, yet their lifetime requirements differ. That difference should be understood before someone proposes a common buffer policy for both. A transformation that suits temporary work may be unnecessary or unhelpful for returned data.

Two compiler changes, two opportunities

Randall's account distinguishes Go 1.25's handling of certain variable-sized slice allocations from Go 1.26's use of speculative stack storage at append sites. In the earlier improvement, a small requested size can fit a reserved stack backing store; larger requests still use the heap. The later improvement can avoid some initial heap growth while building a slice with append. These are compiler opportunities with conditions, not universal language guarantees. The dated explanation is the source for that distinction.

The account also discusses escaping slices. Intermediate construction can benefit from stack storage even when the final data needs to move to the heap before it leaves the function. “Escapes” therefore does not mean that every intermediate step must incur the old pattern of heap growth. Equally, a final escaping backing store cannot simply remain in a stack frame that disappears. Both sides of that distinction matter.

The article's examples illustrate compiler behavior under their stated conditions. They are not benchmark results for this article or a promise about an unrelated service. This discussion runs no local benchmark and assigns no percentage improvement to an application's latency, memory use or cost. Those outcomes require measurements of the application itself.

Manual capacity has a cost too

A capacity estimate can be useful when the program already knows something reliable about the batch. Reserving room may avoid repeated growth. But adding an estimate to an API also asks callers to supply information and maintain the assumption behind it. If the compiler can already handle the small temporary case well, that extra interface may buy less than its source code suggests.

An overestimate has a different cost from an underestimate. Reserving too much space can retain unnecessary memory; reserving too little can still require growth. A typical batch size does not establish the full distribution, particularly when rare large requests drive memory pressure. Review the intended workload rather than designing around the most convenient example.

This does not justify removing every preallocation. The strongest counterargument is that large slices and escaping data remain real sources of heap work, and a known final size can still be valuable. Speculative stack space has limits. A performance-sensitive path may benefit from explicit capacity even after the compiler improvements. The appropriate claim is conditional: keep the optimization when evidence supports its value for the supported toolchain and workload.

Ask the measurement to answer a decision

A useful investigation begins with a concrete concern, such as allocation pressure in one batch-processing path. Compiler diagnostics can help explain escape decisions. Benchmarks or profiles can then assess the relevant behavior under representative input sizes. Those are suggested methods, not tests claimed to have been performed here. The compiler version belongs in the record because its placement decisions can change.

Measurements should also account for what the application retains. A lower allocation count can coexist with a larger retained buffer. An improvement on a small batch can coexist with poor behavior when batches grow. Time, allocated bytes and retained memory describe different aspects of the result. Choose the evidence that answers the actual operating concern instead of treating one counter as a complete verdict.

Correctness remains the first boundary. A buffer reuse scheme that makes ownership confusing or lets one caller observe another caller's data needs scrutiny regardless of its allocation count. The compiler's improvements are attractive partly because they can preserve ordinary source structure. Manual machinery should justify both its performance value and the complexity it introduces.

For custom software development, this changes the optimization conversation. Start with the lifetime and workload, then decide whether the compiler already handles the common path and whether explicit capacity still helps. The February explanation makes source-level allocation counting less persuasive. A reviewable decision connects the supported Go version to the data the program actually has to keep alive.