gotosocial/vendor/github.com/vmihailenco/bufpool/pool.go

149 lines
3 KiB
Go
Raw Normal View History

package bufpool
import (
"math/bits"
"sync/atomic"
)
const (
minBitSize = 6 // 2**6=64 is a CPU cache line size
steps = 20
minSize = 1 << minBitSize // 64 bytes
maxSize = 1 << (minBitSize + steps - 1) // 32 mb
maxPoolSize = maxSize << 1 // 64 mb
defaultServePctile = 0.95
calibrateCallsThreshold = 42000
defaultSize = 4096
)
// Pool represents byte buffer pool.
//
// Different pools should be used for different usage patterns to achieve better
// performance and lower memory usage.
type Pool struct {
calls [steps]uint32
calibrating uint32
ServePctile float64 // default is 0.95
serveSize uint32
}
func (p *Pool) getServeSize() int {
size := atomic.LoadUint32(&p.serveSize)
if size > 0 {
return int(size)
}
for i := 0; i < len(p.calls); i++ {
calls := atomic.LoadUint32(&p.calls[i])
if calls > 10 {
size := indexSize(i)
atomic.CompareAndSwapUint32(&p.serveSize, 0, uint32(size))
return size
}
}
return defaultSize
}
// Get returns an empty buffer from the pool. Returned buffer capacity
// is determined by accumulated usage stats and changes over time.
//
// The buffer may be returned to the pool using Put or retained for further
// usage. In latter case buffer length must be updated using UpdateLen.
func (p *Pool) Get() *Buffer {
buf := Get(p.getServeSize())
buf.Reset()
return buf
}
// New returns an empty buffer bypassing the pool. Returned buffer capacity
// is determined by accumulated usage stats and changes over time.
func (p *Pool) New() *Buffer {
return NewBuffer(make([]byte, 0, p.getServeSize()))
}
// Put returns buffer to the pool.
func (p *Pool) Put(buf *Buffer) {
length := buf.Len()
if length == 0 {
length = buf.Cap()
}
p.UpdateLen(length)
// Always put buf to the pool.
Put(buf)
}
// UpdateLen updates stats about buffer length.
func (p *Pool) UpdateLen(bufLen int) {
idx := index(bufLen)
if atomic.AddUint32(&p.calls[idx], 1) > calibrateCallsThreshold {
p.calibrate()
}
}
func (p *Pool) calibrate() {
if !atomic.CompareAndSwapUint32(&p.calibrating, 0, 1) {
return
}
var callSum uint64
var calls [steps]uint32
for i := 0; i < len(p.calls); i++ {
n := atomic.SwapUint32(&p.calls[i], 0)
calls[i] = n
callSum += uint64(n)
}
serveSum := uint64(float64(callSum) * p.getServePctile())
var serveSize int
callSum = 0
for i, numCall := range &calls {
callSum += uint64(numCall)
if serveSize == 0 && callSum >= serveSum {
serveSize = indexSize(i)
break
}
}
atomic.StoreUint32(&p.serveSize, uint32(serveSize))
atomic.StoreUint32(&p.calibrating, 0)
}
func (p *Pool) getServePctile() float64 {
if p.ServePctile > 0 {
return p.ServePctile
}
return defaultServePctile
}
func index(n int) int {
if n == 0 {
return 0
}
idx := bits.Len32(uint32((n - 1) >> minBitSize))
if idx >= steps {
idx = steps - 1
}
return idx
}
func prevIndex(n int) int {
next := index(n)
if next == 0 || n == indexSize(next) {
return next
}
return next - 1
}
func indexSize(idx int) int {
return minSize << uint(idx)
}