forked from mirrors/gotosocial
97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
|
package bufpool
|
||
|
|
||
|
import (
|
||
|
"sync"
|
||
|
|
||
|
"git.iim.gay/grufwub/go-bytes"
|
||
|
)
|
||
|
|
||
|
// MAX returns the maximum possible sized slice that can be stored in a BufferPool
|
||
|
func MAX() int {
|
||
|
return log2Max
|
||
|
}
|
||
|
|
||
|
// BufferPool is a variable sized buffer pool, separated into memory pages increasing
|
||
|
// by powers of 2. This can offer large improvements over a sync.Pool designed to allocate
|
||
|
// buffers of single sizes, or multiple buffer pools of differing allocation sizes
|
||
|
type BufferPool struct {
|
||
|
noCopy noCopy //nolint
|
||
|
|
||
|
// pools is a predefined-length array of sync.Pools, handling
|
||
|
// ranges in capacity of 2**(n) --> 2**(n+1)
|
||
|
pools [log2MaxPower + 1]sync.Pool
|
||
|
once sync.Once
|
||
|
}
|
||
|
|
||
|
// init simply sets the allocator funcs for each of the pools
|
||
|
func (p *BufferPool) init() {
|
||
|
for i := range p.pools {
|
||
|
p.pools[i].New = func() interface{} {
|
||
|
return &bytes.Buffer{}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get retrieves a Buffer of at least supplied capacity from the pool,
|
||
|
// allocating only if strictly necessary. If a capacity above the maximum
|
||
|
// supported (see .MAX()) is requested, a slice is allocated with
|
||
|
// expectance that it will just be dropped on call to .Put()
|
||
|
func (p *BufferPool) Get(cap int) *bytes.Buffer {
|
||
|
// If cap out of bounds, just alloc
|
||
|
if cap < 2 || cap > log2Max {
|
||
|
buf := bytes.NewBuffer(make([]byte, 0, cap))
|
||
|
return &buf
|
||
|
}
|
||
|
|
||
|
// Ensure initialized
|
||
|
p.once.Do(p.init)
|
||
|
|
||
|
// Calculate page idx from log2 table
|
||
|
pow := uint8(log2Table[cap])
|
||
|
pool := &p.pools[pow-1]
|
||
|
|
||
|
// Attempt to fetch buf from pool
|
||
|
buf := pool.Get().(*bytes.Buffer)
|
||
|
|
||
|
// Check of required capacity
|
||
|
if buf.Cap() < cap {
|
||
|
// We allocate via this method instead
|
||
|
// of by buf.Guarantee() as this way we
|
||
|
// can allocate only what the user requested.
|
||
|
//
|
||
|
// buf.Guarantee() can allocate alot more...
|
||
|
buf.B = make([]byte, 0, cap)
|
||
|
}
|
||
|
|
||
|
return buf
|
||
|
}
|
||
|
|
||
|
// Put resets and place the supplied Buffer back in its appropriate pool. Buffers
|
||
|
// Buffers below or above maximum supported capacity (see .MAX()) will be dropped
|
||
|
func (p *BufferPool) Put(buf *bytes.Buffer) {
|
||
|
// Drop out of size range buffers
|
||
|
if buf.Cap() < 2 || buf.Cap() > log2Max {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Ensure initialized
|
||
|
p.once.Do(p.init)
|
||
|
|
||
|
// Calculate page idx from log2 table
|
||
|
pow := uint8(log2Table[buf.Cap()])
|
||
|
pool := &p.pools[pow-1]
|
||
|
|
||
|
// Reset, place in pool
|
||
|
buf.Reset()
|
||
|
pool.Put(buf)
|
||
|
}
|
||
|
|
||
|
//nolint
|
||
|
type noCopy struct{}
|
||
|
|
||
|
//nolint
|
||
|
func (n *noCopy) Lock() {}
|
||
|
|
||
|
//nolint
|
||
|
func (n *noCopy) Unlock() {}
|