123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- package bytebufferpool
- import (
- "sort"
- "sync"
- "sync/atomic"
- )
- const (
- minBitSize = 6 // 2**6=64 is a CPU cache line size
- steps = 20
- minSize = 1 << minBitSize
- maxSize = 1 << (minBitSize + steps - 1)
- calibrateCallsThreshold = 42000
- maxPercentile = 0.95
- )
- // Pool represents byte buffer pool.
- //
- // Distinct pools may be used for distinct types of byte buffers.
- // Properly determined byte buffer types with their own pools may help reducing
- // memory waste.
- type Pool struct {
- calls [steps]uint64
- calibrating uint64
- defaultSize uint64
- maxSize uint64
- pool sync.Pool
- }
- var defaultPool Pool
- // Get returns an empty byte buffer from the pool.
- //
- // Got byte buffer may be returned to the pool via Put call.
- // This reduces the number of memory allocations required for byte buffer
- // management.
- func Get() *ByteBuffer { return defaultPool.Get() }
- // Get returns new byte buffer with zero length.
- //
- // The byte buffer may be returned to the pool via Put after the use
- // in order to minimize GC overhead.
- func (p *Pool) Get() *ByteBuffer {
- v := p.pool.Get()
- if v != nil {
- return v.(*ByteBuffer)
- }
- return &ByteBuffer{
- B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
- }
- }
- // Put returns byte buffer to the pool.
- //
- // ByteBuffer.B mustn't be touched after returning it to the pool.
- // Otherwise data races will occur.
- func Put(b *ByteBuffer) { defaultPool.Put(b) }
- // Put releases byte buffer obtained via Get to the pool.
- //
- // The buffer mustn't be accessed after returning to the pool.
- func (p *Pool) Put(b *ByteBuffer) {
- idx := index(len(b.B))
- if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
- p.calibrate()
- }
- maxSize := int(atomic.LoadUint64(&p.maxSize))
- if maxSize == 0 || cap(b.B) <= maxSize {
- b.Reset()
- p.pool.Put(b)
- }
- }
- func (p *Pool) calibrate() {
- if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
- return
- }
- a := make(callSizes, 0, steps)
- var callsSum uint64
- for i := uint64(0); i < steps; i++ {
- calls := atomic.SwapUint64(&p.calls[i], 0)
- callsSum += calls
- a = append(a, callSize{
- calls: calls,
- size: minSize << i,
- })
- }
- sort.Sort(a)
- defaultSize := a[0].size
- maxSize := defaultSize
- maxSum := uint64(float64(callsSum) * maxPercentile)
- callsSum = 0
- for i := 0; i < steps; i++ {
- if callsSum > maxSum {
- break
- }
- callsSum += a[i].calls
- size := a[i].size
- if size > maxSize {
- maxSize = size
- }
- }
- atomic.StoreUint64(&p.defaultSize, defaultSize)
- atomic.StoreUint64(&p.maxSize, maxSize)
- atomic.StoreUint64(&p.calibrating, 0)
- }
- type callSize struct {
- calls uint64
- size uint64
- }
- type callSizes []callSize
- func (ci callSizes) Len() int {
- return len(ci)
- }
- func (ci callSizes) Less(i, j int) bool {
- return ci[i].calls > ci[j].calls
- }
- func (ci callSizes) Swap(i, j int) {
- ci[i], ci[j] = ci[j], ci[i]
- }
- func index(n int) int {
- n--
- n >>= minBitSize
- idx := 0
- for n > 0 {
- n >>= 1
- idx++
- }
- if idx >= steps {
- idx = steps - 1
- }
- return idx
- }
|