pool.go 2.9 KB

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