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 }