Optimize Memory Usage With slices.Clip

Problem: Unused slice capacity wastes memory

Slices in Go are dynamic arrays with a length and capacity. When you shorten a slice using [:n], the underlying array retains its capacity. This may be useful if the shortened slice will grow to about the array's size again, but if the short slice will remain short or is unlikely to grow back to the old size (and the context allows to know this at compile time), you might want to return the unused memory back to the operating system. (Talking about long-running apps here.)

Solution: Use slices.Clip to reclaim unused capacity and reduce memory overhead.

How It Works

When you create a large slice and then resize it (e.g., by slicing it to a smaller size), the original backing array still holds onto the allocated memory. slices.Clip trims the slice’s capacity to match its length. The runtime then can get rid of the unused memory.

In a nutshell:

large := make([]int, 1_000_000) // 1M elements
clipped := slices.Clip(large[:10]) // Trim capacity

Some heap stats printing (taken from the full code example in the Playground confirms the effectiveness:

Baseline mem   : HeapInuse = 0.41 MB
Before clipping: HeapInuse = 8.03 MB
   clipped's capacity: 10
After clipping : HeapInuse = 8.03 MB
After cleanup  : HeapInuse = 0.40 MB)

So if you have a long-running app that generates possibly long-lived large slices that might not grow to their previous size after reuse, consider clipping the slice.

I hear you asking: what about simply creating a new slice and copying the content over, like so:

oldSlice := largeSlice[:10]
newSlice := make([]byte, len(oldSlice))
copy(newSlice, oldSlice)

Well, the slices.Clip variant is -

  1. Cleaner: One line versus three lines of boilerplate
  2. Clearer: Clearly communicates to other developers what you're trying to accomplish
  3. Optimized-er (ouch): The compiler can potentially optimize slices.Clip() better than manual allocation plus copying