Description
I'm proposing to extend sync.Pool to allow Put() and Get() of multiple objects in a single call. This proposal could be implemented by adding a few new methods:
// PutFromSlice adds all entries from xs to the pool.
//
// It behaves like the following code:
// for _, x := range xs {
// pool.Put(x)
// }
func (p *Pool[T]) PutFromSlice(xs []*T)
func PoolPutFromSlice[T any](p *Pool, xs []*T) // workaround for no type params on Pool
// FillSlice selects arbitrary items from the Pool, removes them from the Pool,
// and places them into xs.
//
// If there is not enough items to fill the slice, the remaining part of the
// slice is filled with nils. But if p.New is non-nil, the remaining part is
// filled with the results of calling p.New repeatedly.
//
// It behaves like the following code:
// for i := range xs {
// if x := pool.Get(); x != nil {
// xs[i] = x.(*T) // panic if conversion fails
// } else {
// xs[i] = nil
// }
// }
func (p *Pool[T]) FillSlice(xs []*T)
func PoolFillSlice[T any](p *Pool, xs []*T) // workaround for no type params on Pool
Without type params the ergonomics of the API would be beyond disgusting. Without it the API would need to take in a slice of []any
requiring the users to convert their slices to that type. Or perhaps it would require something equally abhorrent.
The motivation is performance. It's starting to become a pattern in my hot path to have a slice of objects to free or to need. Right now I'm just calling Get/Put in a loop to fill/release such slices. One unfortunately common detail that I have going on is that one goroutine does the Gets and after some processing it's some another goroutine that Puts those objects back to the pool. When under load this ends up meaning that the goroutine doing the Gets mostly ends up taking the slow path of Get(). Get() of a slice of objects surely allows new optimizations in the slow path considering how complex of a data structure Pool is.