1. Targeting the problem
During the programming and development process, we often have scenarios of creating similar objects. Such operations may have an impact on performance. A more common practice is to use an object pool. When we need to create an object, we first look up from the object pool. If there are free objects, remove the object from the object pool and return it to the caller for use. Only when there are no free objects in the pool will a new object be created.
On the other hand, for used objects, we will not destroy them, but put them back to the object pool for subsequent use. Using the object pool can greatly improve performance when frequently creating and destroying objects. At the same time, in order to avoid the objects in the object pool occupying too much memory, the object pool is generally equipped with a specific cleaning strategy, Go's standard libraryThis is an example of this.
The objects in it will be cleaned up by garbage collection
Among these objects, there is a special one that is byte slice. When doing string splicing, in order to efficient splicing, we usually store the intermediate result in a byte buffer. After splicing, we will generate a string from the byte buffer.
Go standard libraryEncapsulated byte slices and provide some usage interfaces. We know that the capacity of slices is limited. When the capacity is insufficient, capacity expansion is required. Frequent capacity expansion can easily cause performance jitter.
bytebufferpool
Realize your ownBuffer
Type and introduce a simple algorithm to reduce the performance losses caused by expansion
2. How to use
bytebufferpool
Very light access
func main() { bf := () ("Hello") (" World!!") (()) }
The above usage usesdefaultPool
,bytebufferpool
ofPool
The object is public or can be created by yourself
3. Source code analysis
bytebufferpool
How to minimize memory allocation and waste? Let’s look at the entire macro first.Pool
Then refine the definition of the relevant methods to find the answer
bytebufferpool
middlePool
The definition of a structure is
type Pool struct { calls [steps]uint64 calibrating uint64 defaultSize uint64 maxSize uint64 pool }
incalls
Stores the number of objects of different sizes in a certain interval.calibrating
is a flag bit that marks the current onePool
Is it being re-planned?defaultSize
It is the default size when the element is newly created, and its selection logic is the current one.calls
The maximum interval value corresponding to the object with the most occurrences in the object, which can prevent frequent expansion after being fetched from the object pool.maxSize
Restricted to placePool
The size of the largest element in prevents excessive memory from taking up some large objects
bytebufferpool
Some anddefaultSize
andmaxSize
Calculate related constants
const ( minBitSize = 6 // 2**6=64 is a CPU cache line size steps = 20 minSize = 1 << minBitSize maxSize = 1 << (minBitSize + steps - 1) calibrateCallsThreshold = 42000 maxPercentile = 0.95 )
inminBitSize
It represents the maximum value of the first interval object size (2 to the power of xx-1), inbytebufferpool
In the object size is divided into 20 intervals, that issteps
, the first interval is[0, 2^6-1]
, the second one is[2^6, 2^7-1]
..., and so on
calibrateCallsThreshold
Indicates that if the number of objects in a certain interval exceeds this threshold,Pool
The variables inmaxPercentile
Used for calculationPool
In-housemaxSize
, indicating the previous95%
Element size
bytebufferpool
There are fewer methods in it, the core isGet
andPut
method
- Get
func (p *Pool) Get() *ByteBuffer { v := () if v != nil { return v.(*ByteBuffer) } return &ByteBuffer{ B: make([]byte, 0, atomic.LoadUint64(&)), } }
You can see that if there are no objects in the object pool, you will apply.defaultSize
Slices of size return
- Put
func (p *Pool) Put(b *ByteBuffer) { idx := index(len()) if atomic.AddUint64(&[idx], 1) > calibrateCallsThreshold { () } maxSize := int(atomic.LoadUint64(&)) if maxSize == 0 || cap() <= maxSize { () (b) } }
The Put method will be more troublesome, let's take a look at it in steps
- Calculate the element in
calls
Position in the array
func index(n int) int { n-- n >>= minBitSize idx := 0 for n > 0 { n >>= 1 idx++ } if idx >= steps { idx = steps - 1 } return idx }
The logic here is to move the length right firstminBitSize
, if it is still greater than 0, then move one right one each time, add idx to 1, and finally if idx exceeds the totalsteps
(20), then the position is in the last interval
- Determine whether the number of elements placed in the current interval exceeds the
calibrateCallsThreshold
The specified threshold value is exceeded and recalculate itPool
The value of the element in
func (p *Pool) calibrate() { // If recalculation is being performed, return to control multiple concurrency if !atomic.CompareAndSwapUint64(&, 0, 1) { return } // Calculate the number of elements & total number of elements in each section a := make(callSizes, 0, steps) var callsSum uint64 for i := uint64(0); i < steps; i++ { calls := atomic.SwapUint64(&[i], 0) callsSum += calls a = append(a, callSize{ calls: calls, size: minSize << i, }) } // Sort from large to small by the number of object elements (a) // defaultSize is the default size of the internal slice, reducing the number of expansions // maxSize limits the maximum element size placed in the pool defaultSize := a[0].size maxSize := defaultSize // Give the maximum size in the first 95% element to maxSize maxSum := uint64(float64(callsSum) * maxPercentile) callsSum = 0 for i := 0; i < steps; i++ { if callsSum > maxSum { break } callsSum += a[i].calls size := a[i].size if size > maxSize { maxSize = size } } // Assign defaultSize and maxSize atomic.StoreUint64(&, defaultSize) atomic.StoreUint64(&, maxSize) atomic.StoreUint64(&, 0) }
- Determine whether the size of the currently placed element exceeds the
maxSize
, if it exceeds the value, it will not be placed in the object pool
The above is the detailed explanation of the use of go object pooling component bytebufferpool. For more information about go bytebufferpool, please follow my other related articles!