According to sizeclasses.go, allocating any amount of space greater than the previous size class and below the current size class is inefficient (e.g., allocating 257B is 11% wasteful since it allocates 288B anyways).
The current Buffer.grow algorithm is inefficient since it may choose a buffer size that is in-between size classes, rather than optimally using the entire size class.
var bb bytes.Buffer
bb.Write(make([]byte, 1<<12))
fmt.Println(cap(bb.Bytes())) // exactly 1<<12
bb.WriteByte('a')
fmt.Println(cap(bb.Bytes())) // exactly (1<<13) + 1, 13.5% waste
While we should avoid teaching Buffer.grow about size classes, it could choose values that align with 2n or 2n+2n-1, which would have a high probability of lying exactly on a type-class boundary.
Related, for your entertainment: https://commaok.xyz/post/discovering-size-classes/
If buffer.grow would use append+make slice extending idiom to grow it could get that benefit without using sizeclasses: #21266
A size class aware make would also be an option if an addition to the Go language would be accepted: #24204
/cc @bradfitz @ianlancetaylor
If buffer.grow would use append+make slice extending idiom to grow it could get that benefit without using sizeclasses
The current implementation of append has a growth rate that approaches 1.25x as the slice gets larger.
The current implementation of Buffer.grow has a growth rate of 2x.
While a growth rate of 1.25x is more memory efficient, it's probably slower due to the increased number of allocations+copying. Switching the growth rate from 2x to 1.25x seems to be a more substantial change than just addressing the wasted space.
Nevermind, ignore my comment, I just realized an append+make effectively allows you control the growth rate by controlling the size of the make.
Most helpful comment
If buffer.grow would use append+make slice extending idiom to grow it could get that benefit without using sizeclasses: #21266
A size class aware make would also be an option if an addition to the Go language would be accepted: #24204