1 | package bytebufferpool
|
---|
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 | }
|
---|