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 }