github.com/pubgo/xprocess@v0.1.11/xprocess_bytebuffer/pool.go (about)

     1  package xprocess_bytebuffer
     2  
     3  import (
     4  	"sort"
     5  	"sync"
     6  	"sync/atomic"
     7  )
     8  
     9  const (
    10  	minBitSize = 6 // 2**6=64 is a CPU cache line size
    11  	steps      = 20
    12  
    13  	minSize = 1 << minBitSize
    14  	maxSize = 1 << (minBitSize + steps - 1)
    15  
    16  	calibrateCallsThreshold = 42000
    17  	maxPercentile           = 0.95
    18  )
    19  
    20  // Pool represents byte buffer pool.
    21  //
    22  // Distinct pools may be used for distinct types of byte buffers.
    23  // Properly determined byte buffer types with their own pools may help reducing
    24  // memory waste.
    25  type Pool struct {
    26  	calls       [steps]uint64
    27  	calibrating uint64
    28  
    29  	defaultSize uint64
    30  	maxSize     uint64
    31  
    32  	pool sync.Pool
    33  }
    34  
    35  var defaultPool Pool
    36  
    37  // Get returns an empty byte buffer from the pool.
    38  //
    39  // Got byte buffer may be returned to the pool via Put call.
    40  // This reduces the number of memory allocations required for byte buffer
    41  // management.
    42  func Get() *ByteBuffer { return defaultPool.Get() }
    43  
    44  // Get returns new byte buffer with zero length.
    45  //
    46  // The byte buffer may be returned to the pool via Put after the use
    47  // in order to minimize GC overhead.
    48  func (p *Pool) Get() *ByteBuffer {
    49  	v := p.pool.Get()
    50  	if v != nil {
    51  		return v.(*ByteBuffer)
    52  	}
    53  	return &ByteBuffer{
    54  		B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
    55  	}
    56  }
    57  
    58  // Put returns byte buffer to the pool.
    59  //
    60  // ByteBuffer.B mustn't be touched after returning it to the pool.
    61  // Otherwise data races will occur.
    62  func Put(b *ByteBuffer) { defaultPool.Put(b) }
    63  
    64  // Put releases byte buffer obtained via Get to the pool.
    65  //
    66  // The buffer mustn't be accessed after returning to the pool.
    67  func (p *Pool) Put(b *ByteBuffer) {
    68  	idx := index(len(b.B))
    69  
    70  	if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
    71  		p.calibrate()
    72  	}
    73  
    74  	maxSize := int(atomic.LoadUint64(&p.maxSize))
    75  	if maxSize == 0 || cap(b.B) <= maxSize {
    76  		b.Reset()
    77  		p.pool.Put(b)
    78  	}
    79  }
    80  
    81  func (p *Pool) calibrate() {
    82  	if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
    83  		return
    84  	}
    85  
    86  	a := make(callSizes, 0, steps)
    87  	var callsSum uint64
    88  	for i := uint64(0); i < steps; i++ {
    89  		calls := atomic.SwapUint64(&p.calls[i], 0)
    90  		callsSum += calls
    91  		a = append(a, callSize{
    92  			calls: calls,
    93  			size:  minSize << i,
    94  		})
    95  	}
    96  	sort.Sort(a)
    97  
    98  	defaultSize := a[0].size
    99  	maxSize := defaultSize
   100  
   101  	maxSum := uint64(float64(callsSum) * maxPercentile)
   102  	callsSum = 0
   103  	for i := 0; i < steps; i++ {
   104  		if callsSum > maxSum {
   105  			break
   106  		}
   107  		callsSum += a[i].calls
   108  		size := a[i].size
   109  		if size > maxSize {
   110  			maxSize = size
   111  		}
   112  	}
   113  
   114  	atomic.StoreUint64(&p.defaultSize, defaultSize)
   115  	atomic.StoreUint64(&p.maxSize, maxSize)
   116  
   117  	atomic.StoreUint64(&p.calibrating, 0)
   118  }
   119  
   120  type callSize struct {
   121  	calls uint64
   122  	size  uint64
   123  }
   124  
   125  type callSizes []callSize
   126  
   127  func (ci callSizes) Len() int {
   128  	return len(ci)
   129  }
   130  
   131  func (ci callSizes) Less(i, j int) bool {
   132  	return ci[i].calls > ci[j].calls
   133  }
   134  
   135  func (ci callSizes) Swap(i, j int) {
   136  	ci[i], ci[j] = ci[j], ci[i]
   137  }
   138  
   139  func index(n int) int {
   140  	n--
   141  	n >>= minBitSize
   142  	idx := 0
   143  	for n > 0 {
   144  		n >>= 1
   145  		idx++
   146  	}
   147  	if idx >= steps {
   148  		idx = steps - 1
   149  	}
   150  	return idx
   151  }