github.com/oarkflow/sio@v0.0.6/internal/bpool/pool.go (about)

     1  package bpool
     2  
     3  import (
     4  	"sort"
     5  	"sync"
     6  	"sync/atomic"
     7  )
     8  
     9  const (
    10  	defaultMinBitSize = 6 // 2**6=64 is a CPU cache line size
    11  	steps             = 20
    12  
    13  	defaultMinSize = 1 << defaultMinBitSize
    14  	defaultMaxSize = 1 << (defaultMinBitSize + 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  	minBitSize uint64
    33  	minSize    uint64
    34  
    35  	pool sync.Pool
    36  }
    37  
    38  var defaultPool Pool
    39  
    40  // Get returns an empty byte buffer from the pool.
    41  //
    42  // Got byte buffer may be returned to the pool via Put call.
    43  // This reduces the number of memory allocations required for byte buffer
    44  // management.
    45  func Get() *ByteBuffer { return defaultPool.Get() }
    46  
    47  // Get returns new byte buffer with zero length.
    48  //
    49  // The byte buffer may be returned to the pool via Put after the use
    50  // in order to minimize GC overhead.
    51  func (p *Pool) Get() *ByteBuffer {
    52  	v := p.pool.Get()
    53  	if v != nil {
    54  		b := v.(*ByteBuffer)
    55  		b.Reset()
    56  		return b
    57  	}
    58  	return &ByteBuffer{
    59  		B: make([]byte, 0, atomic.LoadUint64(&p.defaultSize)),
    60  	}
    61  }
    62  
    63  // GetLen returns a buufer with its
    64  // []byte slice of the exact len as specified
    65  //
    66  // The byte buffer may be returned to the pool via Put after the use
    67  // in order to minimize GC overhead.
    68  func GetLen(s int) *ByteBuffer { return defaultPool.GetLen(s) }
    69  
    70  // GetLen return a buufer with its
    71  // []byte slice of the exact len as specified
    72  //
    73  // The byte buffer may be returned to the pool via Put after the use
    74  // in order to minimize GC overhead.
    75  func (p *Pool) GetLen(s int) *ByteBuffer {
    76  	v := p.pool.Get()
    77  	if v == nil {
    78  		size := int(p.minSize << uint(index(p.minBitSize, s)))
    79  		if size < s {
    80  			size = s
    81  		}
    82  		return &ByteBuffer{
    83  			B: make([]byte, s, size),
    84  		}
    85  	}
    86  
    87  	b := v.(*ByteBuffer)
    88  	if cap(b.B) >= s {
    89  		b.B = b.B[:s]
    90  		return b
    91  	}
    92  
    93  	// The size is smaller, return it to the pool and create another one
    94  	p.pool.Put(b)
    95  	size := int(p.minSize << uint(index(p.minBitSize, s)))
    96  	if size < s {
    97  		size = s
    98  	}
    99  	return &ByteBuffer{
   100  		B: make([]byte, s, size),
   101  	}
   102  }
   103  
   104  // Put returns byte buffer to the pool.
   105  //
   106  // ByteBuffer.B mustn't be touched after returning it to the pool.
   107  // Otherwise data races will occur.
   108  func Put(b *ByteBuffer) { defaultPool.Put(b) }
   109  
   110  // Put releases byte buffer obtained via Get to the pool.
   111  //
   112  // The buffer mustn't be accessed after returning to the pool.
   113  func (p *Pool) Put(b *ByteBuffer) {
   114  	if p.minBitSize == 0 {
   115  		p.initBins()
   116  	}
   117  
   118  	idx := index(p.minBitSize, len(b.B))
   119  
   120  	if atomic.AddUint64(&p.calls[idx], 1) > calibrateCallsThreshold {
   121  		p.calibrate()
   122  	}
   123  
   124  	maxSize := int(atomic.LoadUint64(&p.maxSize))
   125  	if maxSize == 0 || cap(b.B) <= maxSize {
   126  		p.pool.Put(b)
   127  	}
   128  }
   129  
   130  func (p *Pool) calibrate() {
   131  	if !atomic.CompareAndSwapUint64(&p.calibrating, 0, 1) {
   132  		return
   133  	}
   134  
   135  	if p.minBitSize == 0 {
   136  		p.initBins()
   137  	}
   138  
   139  	a := make(callSizes, 0, steps)
   140  	var callsSum uint64
   141  	for i := uint64(0); i < steps; i++ {
   142  		calls := atomic.SwapUint64(&p.calls[i], 0)
   143  		callsSum += calls
   144  		a = append(a, callSize{
   145  			calls: calls,
   146  			size:  p.minSize << i,
   147  		})
   148  	}
   149  	if p.minBitSize+steps < 32 && a[steps-1].calls > a[0].calls {
   150  		// Increase the first bin's size
   151  		p.resizeBins(p.minBitSize + 1)
   152  	} else if p.minBitSize > defaultMinBitSize &&
   153  		a[0].calls > 0 &&
   154  		a[steps-2].calls == 0 &&
   155  		a[steps-1].calls == 0 {
   156  		// Decrease the size of first bin's size
   157  		p.resizeBins(p.minBitSize - 1)
   158  	}
   159  	sort.Sort(a)
   160  
   161  	defaultSize := a[0].size
   162  	maxSize := defaultSize
   163  
   164  	maxSum := uint64(float64(callsSum) * maxPercentile)
   165  	callsSum = 0
   166  	for i := 0; i < steps; i++ {
   167  		if callsSum > maxSum {
   168  			break
   169  		}
   170  		callsSum += a[i].calls
   171  		size := a[i].size
   172  		if size > maxSize {
   173  			maxSize = size
   174  		}
   175  	}
   176  
   177  	atomic.StoreUint64(&p.defaultSize, defaultSize)
   178  	atomic.StoreUint64(&p.maxSize, maxSize)
   179  
   180  	atomic.StoreUint64(&p.calibrating, 0)
   181  }
   182  
   183  func (p *Pool) resizeBins(minBitSize uint64) {
   184  	atomic.StoreUint64(&p.minBitSize, minBitSize)
   185  	atomic.StoreUint64(&p.minSize, 1<<minBitSize)
   186  }
   187  
   188  func (p *Pool) initBins() {
   189  	atomic.StoreUint64(&p.minBitSize, defaultMinBitSize)
   190  	atomic.StoreUint64(&p.minSize, 1<<defaultMinBitSize)
   191  }
   192  
   193  type callSize struct {
   194  	calls uint64
   195  	size  uint64
   196  }
   197  
   198  type callSizes []callSize
   199  
   200  func (ci callSizes) Len() int {
   201  	return len(ci)
   202  }
   203  
   204  func (ci callSizes) Less(i, j int) bool {
   205  	return ci[i].calls > ci[j].calls
   206  }
   207  
   208  func (ci callSizes) Swap(i, j int) {
   209  	ci[i], ci[j] = ci[j], ci[i]
   210  }
   211  
   212  func index(minBitSize uint64, n int) int {
   213  	n--
   214  	n >>= minBitSize
   215  	idx := 0
   216  	for n > 0 {
   217  		n >>= 1
   218  		idx++
   219  	}
   220  	if idx >= steps {
   221  		idx = steps - 1
   222  	}
   223  	return idx
   224  }