152 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			152 lines
		
	
	
		
			2.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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
 | |
| }
 |