Sync.Pool is a go-Zero Pool. Sync is a go-Zero Pool. This paper mainly analyzes the thinking direction of sync.Pool design and free-lock.
The former 🧂
Whether it is connection pool, coroutine pool, etc., the original design is to solve the creation of objects (resources) time, through pre-creation to reduce business time to improve performance; Of course, the size of the pool is also certain, which can prevent infinite expansion of resources to a certain extent. Of course, the expansion also needs to be recycled, which will cause longer GC and STW time.
That’s the purpose of the design. Sync. Pool: Pre-Go 1.13 issues
GC
All objects are recycled; After recycling,Get
Hit ratio drops, repeat and create another one. andGC
causeSTW
Performance jitter may occur.- use
Mutex
That’s pretty obvious. If multiplegoroutine
Acquisition, contention, and locking are bound to cause performance degradation.
From the perspective of optimization:
GC
Delay collection while based onGet
You can ask for itGC
Reclaim objects again from the container of- Lock this problem:
- It is necessary to lock, then lock granularity reduction, also reduce the scope of competition;
- Direct reduction of competition: allocate resources well in advance, there is no competition;
So the optimization for sync.pool is to reuse objects as much as possible and avoid duplication of creation and destruction.
Of course, there are many ways to do this. This comes down to the actual code logic, so here we go.
Pool struct
type Pool struct {
// Ensure that an object is not copied after the first use
noCopy noCopy
// These two are a pair
local unsafe.Pointer // Local queue for each P. The actual type is [P]poolLocal
localSize uintptr // [P]poolLocal size
victim unsafe.Pointer // Go through the garbage can to see if you can recycle anything
victimSize uintptr // victim size
New func(a) interface{} // Call new() when there is no object to retrieve.
}
Copy the code
Note the noCopy check mechanism:
type noCopy struct{} // go vet-copylocks static analysis func (*noCopy) Lock(a) {} func (*noCopy) Unlock(a) {} Copy the code
type poolLocal struct {
poolLocalInternal
// Prevent pseudo sharing
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
type poolLocalInternal struct {
private interface{} Pool lock = Pool lock = Pool lock = Pool lock
shared poolChain // Public cache area
}
type poolChain struct {
// Only producers push to, without locking
head *poolChainElt
// Can only be operated by the consumer, read/write requires atomic control
tail *poolChainElt
}
type poolChainElt struct {
poolDequeue
next, prev *poolChainElt
}
type poolDequeue struct {
// 32-bit head, 32-bit tail pointer
headTail uint64
// ring buffer
vals []eface
}
Copy the code
Get
func (p *Pool) Get(a) interface{} {
// Ignore some race checks......
l, pid := p.pin()
x := l.private
l.private = nil
if x == nil {
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
/ /...
if x == nil&& p.New ! =nil {
x = p.New()
}
return x
}
Copy the code
The process is as follows:
- Will currently run
G
å’ŒP
Bind and return the currentP
The correspondingpoolLocal, pid
If not, look behindpinSlow
Create 】 - take
poolLocal
çš„private
Assigned tox
And thenprivate
Set tonil
- judge
x == nil
, if is empty, fromlocal.shared
Pop a header and assign it tox
- If it’s still empty, get something else
P
çš„shared
Steal an object [getSlow
As the name suggests, it’s very slow, with a big lock. - There’s nowhere else to get the object,
runtime_procUnpin
Remove the binding - If I don’t find it, then
New()
a
Having said the process, pick a few interesting points in the process of watching:
pin
pin()
|- runtime_procPin() / / get the pid
|- pid < localsize -> return local[pid]
|- pinSlow() // It looks very slow
Copy the code
Make ([]poolLocal, psize)
Why is it slow?
func (p *Pool) pinSlow(a) (*poolLocal, int) {
// Lock all pools directly
allPoolsMu.Lock()
defer allPoolsMu.Unlock()
// Contact binding, another P may have been created;
// return local[pid]
// ...
/ / create
size := runtime.GOMAXPROCS(0)
local := make([]poolLocal, size)
atomic.StorePointer(&p.local, unsafe.Pointer(&local[0])) // store-release
atomic.StoreUintptr(&p.localSize, uintptr(size)) // store-release
return &local[pid], pid
}
Copy the code
AllPoolsMu All object pools will be in this array, and the lock will hold the global Pool array.
To be continued…