|
|
@@ -0,0 +1,151 @@
|
|
|
+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
|
|
|
+}
|