github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/perf/bbp/sized.go (about)

     1  package bbp
     2  
     3  import (
     4  	"math/bits"
     5  	"sync"
     6  	"unsafe"
     7  
     8  	"github.com/jxskiss/gopkg/v2/internal/unsafeheader"
     9  )
    10  
    11  const (
    12  	minShift = 6  // at least 64B
    13  	maxShift = 25 // max 32MB
    14  
    15  	// Min and max buffer size provided in this package.
    16  	minBufSize = 1 << minShift
    17  	maxBufSize = 1 << maxShift
    18  
    19  	minPoolIdx = 6
    20  	maxPoolIdx = maxShift
    21  	poolSize   = maxPoolIdx + 1
    22  )
    23  
    24  // Get returns a byte slice from the pool with specified length and capacity.
    25  // When you finish the work with the buffer, you may call Put to put it back
    26  // to the pool for reusing.
    27  func Get(length, capacity int) []byte {
    28  	if capacity > maxBufSize {
    29  		return make([]byte, length, capacity)
    30  	}
    31  
    32  	// Manually inlining.
    33  	// return get(length, capacity)
    34  	idx := indexGet(capacity)
    35  	return sizedPools[idx].Get(length)
    36  }
    37  
    38  // Put puts back a byte slice to the pool for reusing.
    39  //
    40  // The byte slice mustn't be touched after returning it to the pool,
    41  // otherwise data races will occur.
    42  func Put(buf []byte) { put(buf) }
    43  
    44  // Grow checks capacity of buf, it returns a new byte buffer from the pool,
    45  // if necessary, to guarantee space for another n bytes.
    46  // After Grow(n), at least n bytes can be appended to the returned buffer
    47  // without another allocation.
    48  // If n is negative, Grow will panic.
    49  //
    50  // Note that if reuseBuf is true and a new slice is returned, the old
    51  // buf will be put back to the pool, the caller must not retain reference
    52  // to the old buf and must not access it again, else data race happens.
    53  func Grow(buf []byte, n int, reuseBuf bool) []byte {
    54  	if n < 0 {
    55  		panic("bbp.Grow: negative size to grow")
    56  	}
    57  	if cap(buf) >= len(buf)+n {
    58  		return buf
    59  	}
    60  	return grow(buf, len(buf)+n, reuseBuf)
    61  }
    62  
    63  // -------- sized pools -------- //
    64  
    65  var (
    66  	sizedPools [poolSize]*bufPool
    67  )
    68  
    69  func init() {
    70  	for i := 0; i < poolSize; i++ {
    71  		size := 1 << i
    72  		sizedPools[i] = &bufPool{size: size}
    73  	}
    74  }
    75  
    76  type bufPool struct {
    77  	size int
    78  	pool sync.Pool
    79  }
    80  
    81  func (p *bufPool) Get(length int) []byte {
    82  	if ptr := p.pool.Get(); ptr != nil {
    83  		return _toBuf(ptr.(unsafe.Pointer), length)
    84  	}
    85  	return make([]byte, length, p.size)
    86  }
    87  
    88  func (p *bufPool) Put(buf []byte) {
    89  	if cap(buf) >= p.size {
    90  		p.pool.Put(_toPtr(buf))
    91  	}
    92  }
    93  
    94  func _toBuf(ptr unsafe.Pointer, length int) []byte {
    95  	size := *(*int)(ptr)
    96  	return *(*[]byte)(unsafe.Pointer(&unsafeheader.SliceHeader{
    97  		Data: ptr,
    98  		Len:  length,
    99  		Cap:  size,
   100  	}))
   101  }
   102  
   103  func _toPtr(buf []byte) unsafe.Pointer {
   104  	h := *(*unsafeheader.SliceHeader)(unsafe.Pointer(&buf))
   105  	*(*int)(h.Data) = h.Cap
   106  	return h.Data
   107  }
   108  
   109  // callers must guarantee that capacity is not greater than maxBufSize.
   110  func get(length, capacity int) []byte {
   111  	idx := indexGet(capacity)
   112  	return sizedPools[idx].Get(length)
   113  }
   114  
   115  func put(buf []byte) {
   116  	c := cap(buf)
   117  	if c >= minBufSize && c <= maxBufSize {
   118  		idx := indexPut(c)
   119  		ptr := _toPtr(buf)
   120  		sizedPools[idx].pool.Put(ptr)
   121  	}
   122  }
   123  
   124  func grow(buf []byte, capacity int, reuseBuf bool) []byte {
   125  	var newBuf []byte
   126  	if capacity > maxBufSize {
   127  		newBuf = make([]byte, len(buf), capacity)
   128  	} else {
   129  		// Manually inlining.
   130  		// newBuf = get(len(buf), capacity)
   131  		idx := indexGet(capacity)
   132  		newBuf = sizedPools[idx].Get(len(buf))
   133  	}
   134  	copy(newBuf, buf)
   135  	if reuseBuf {
   136  		put(buf)
   137  	}
   138  	return newBuf
   139  }
   140  
   141  // indexGet finds the pool index for the given size to get buffer from,
   142  // if size not equals to a predefined size, it returns the index of the
   143  // next predefined size.
   144  func indexGet(size int) int {
   145  	if size <= minBufSize {
   146  		return minPoolIdx
   147  	}
   148  
   149  	// Manually inline bsr and isPowerOfTwo here.
   150  	idx := bits.Len32(uint32(size))
   151  	if size&(size-1) == 0 {
   152  		idx -= 1 //nolint:revive
   153  	}
   154  	return idx
   155  }
   156  
   157  // indexPut finds the pool index for the given size to put buffer back,
   158  // if size not equals to a predefined size, it returns the index of the
   159  // previous predefined size.
   160  func indexPut(size int) int {
   161  	// Manually inline bsr.
   162  	return bits.Len32(uint32(size)) - 1
   163  }
   164  
   165  // bsr.
   166  //
   167  // Callers within this package guarantee that n doesn't overflow int32.
   168  //
   169  //nolint:unused
   170  func bsr(n int) int {
   171  	return bits.Len32(uint32(n)) - 1
   172  }
   173  
   174  // isPowerOfTwo reports whether n is a power of two.
   175  //
   176  //nolint:unused
   177  func isPowerOfTwo(n int) bool {
   178  	return n&(n-1) == 0
   179  }