fear of code

What io.Copy actually allocates

I spent an afternoon convinced that a proxy I had written was allocating per connection, because the heap profile showed a steady climb that tracked concurrency almost exactly. It was, and the allocation was not mine.

io.Copy allocates a 32KB buffer per call unless the source implements WriterTo or the destination implements ReaderFrom. For a network socket on either side, neither is true in the general case, so every concurrent copy holds 32KB that lives until the copy returns.

buf := make([]byte, 32*1024)
io.CopyBuffer(dst, src, buf)

The fix is io.CopyBuffer with a buffer from a pool. The part that cost me the afternoon is that CopyBuffer ignores the buffer entirely if either side implements those interfaces, so a microbenchmark against bytes.Buffer shows no difference at all and you conclude the theory is wrong.

Benchmark against the thing you actually copy.

All notes · Front page