Go 是一個(gè)自動(dòng)垃圾回收的編程語(yǔ)言,它的算法我們后續(xù)會(huì)講到,主要就是采用三色并發(fā)標(biāo)記算法標(biāo)記對(duì)象并回收。我們可以不用考慮為golang來(lái)節(jié)省什么,但是我們?nèi)绻雽⒊绦蜃龅絻?yōu)秀我們就不得不考慮減少它gc的次數(shù),畢竟,Go 的自動(dòng)垃圾回收機(jī)制還是有一個(gè) STW(stop-the-world,程序暫停)的時(shí)間,而且,大量地創(chuàng)建在堆上的對(duì)象,也會(huì)影響垃圾回收標(biāo)記的時(shí)間
所以,一般我們做性能優(yōu)化的時(shí)候,會(huì)采用對(duì)象池的方式,把不用的對(duì)象回收起來(lái),避免被垃圾回收掉,這樣使用的時(shí)候就不必在堆上重新創(chuàng)建了
按照慣例我們先看官方文檔
官方文檔
// A Pool is a set of temporary objects that may be individually saved and
// retrieved.
//
// Any item stored in the Pool may be removed automatically at any time without
// notification. If the Pool holds the only reference when this happens, the
// item might be deallocated.
//
// A Pool is safe for use by multiple goroutines simultaneously.
//
// Pool's purpose is to cache allocated but unused items for later reuse,
// relieving pressure on the garbage collector. That is, it makes it easy to
// build efficient, thread-safe free lists. However, it is not suitable for all
// free lists.
//
// An appropriate use of a Pool is to manage a group of temporary items
// silently shared among and potentially reused by concurrent independent
// clients of a package. Pool provides a way to amortize allocation overhead
// across many clients.
//
// An example of good use of a Pool is in the fmt package, which maintains a
// dynamically-sized store of temporary output buffers. The store scales under
// load (when many goroutines are actively printing) and shrinks when
// quiescent.
//
// On the other hand, a free list maintained as part of a short-lived object is
// not a suitable use for a Pool, since the overhead does not amortize well in
// that scenario. It is more efficient to have such objects implement their own
// free list.
//
// A Pool must not be copied after first use.
//池是一組臨時(shí)對(duì)象,可以分別保存和
//檢索到。
//
//池中存儲(chǔ)的任何項(xiàng)目都可以隨時(shí)自動(dòng)刪除,而無(wú)需
//通知。如果發(fā)生這種情況時(shí),池中只有唯一的引用,則
//可能已釋放項(xiàng)目。
//
//一個(gè)Pool可以安全地同時(shí)被多個(gè)goroutine使用。
//
//池的目的是緩存已分配但未使用的項(xiàng)目,以供以后重用,
//減輕垃圾收集器的壓力。也就是說(shuō),它很容易
//建立有效的,線程安全的空閑列表。但是,它并不適合所有人
//免費(fèi)列表。
//
//池的適當(dāng)用法是管理一組臨時(shí)項(xiàng)
//在并發(fā)的獨(dú)立服務(wù)器之間靜默共享并有可能被重用
//包的客戶(hù)。池提供了一種攤銷(xiāo)分配開(kāi)銷(xiāo)的方法
//在許多客戶(hù)中。
//
// fmt包中有一個(gè)很好使用Pool的示例,它維護(hù)了一個(gè)
//動(dòng)態(tài)大小的臨時(shí)輸出緩沖區(qū)存儲(chǔ)。店鋪規(guī)模在
//加載(當(dāng)許多goroutine正在活動(dòng)打印時(shí)),并在收縮時(shí)收縮
//靜止。
//
//另一方面,作為短期對(duì)象的一部分維護(hù)的空閑列表是
//不適合作為Pool的用途,因?yàn)殚g接費(fèi)用無(wú)法在
//這種情況。使此類(lèi)對(duì)象實(shí)現(xiàn)自己的效率更高
//空閑列表。
//
//池在第一次使用后不得復(fù)制。
具體使用
sync.Pool 數(shù)據(jù)類(lèi)型用來(lái)保存一組可獨(dú)立訪問(wèn)的臨時(shí)對(duì)象,為什么說(shuō)是臨時(shí),因?yàn)榭赡茈S時(shí)被移除掉,在stw的時(shí)候也有可能被移除掉。所以我們不要使用這個(gè)做長(zhǎng)鏈接保存
pool是一個(gè)線程安全的對(duì)象,池中的東西隨時(shí)可能被銷(xiāo)毀
New
Pool struct 包含一個(gè) New 字段,這個(gè)字段的類(lèi)型是函數(shù) func() interface{}。當(dāng)調(diào)用 Pool 的 Get 方法從池中獲取元素,沒(méi)有更多的空閑元素可返回時(shí),就會(huì)調(diào)用這個(gè) New 方法來(lái)創(chuàng)建新的元素。如果你沒(méi)有設(shè)置 New 字段,沒(méi)有更多的空閑元素可返回時(shí),Get 方法將返回 nil,表明當(dāng)前沒(méi)有可用的元素
Put
這個(gè)方法用于將一個(gè)元素返還給 Pool,Pool 會(huì)把這個(gè)元素保存到池中,并且可以復(fù)用。但如果 Put 一個(gè) nil 值,Pool 就會(huì)忽略這個(gè)值。
Get
如果調(diào)用這個(gè)方法,就會(huì)從 Pool取走一個(gè)元素,這也就意味著,這個(gè)元素會(huì)從 Pool 中移除,返回給調(diào)用者。不過(guò),除了返回值是正常實(shí)例化的元素,Get 方法的返回值還可能會(huì)是一個(gè) nil(Pool.New 字段沒(méi)有設(shè)置,又沒(méi)有空閑元素可以返回),所以你在使用的時(shí)候,可能需要判斷。
sync.pool源碼解析
這個(gè)是池的結(jié)構(gòu)
type Pool struct {
noCopy noCopy //這個(gè)好像是為了使用govet 可以檢測(cè)沖突
local unsafe.Pointer // 這是一個(gè)本地的環(huán)的指針
localSize uintptr // 本地?cái)?shù)組大小
victim unsafe.Pointer // local from previous cycle
victimSize uintptr // size of victims array
// New optionally specifies a function to generate
// a value when Get would otherwise return nil.
// It may not be changed concurrently with calls to Get.
New func() interface{}
}
在這段代碼中,你需要關(guān)注一下 local 字段,因?yàn)樗挟?dāng)前主要的空閑可用的元素都存放在 local 字段中,請(qǐng)求元素時(shí)也是優(yōu)先從 local 字段中查找可用的元素。local 字段包含一個(gè) poolLocalInternal 字段,并提供 CPU 緩存對(duì)齊,從而避免 false sharing。
// Local per-P Pool appendix.
type poolLocalInternal struct {
private interface{} // Can be used only by the respective P.
shared poolChain // Local P can pushHead/popHead; any P can popTail.
}
type poolLocal struct {
poolLocalInternal
// Prevents false sharing on widespread platforms with
// 128 mod (cache line size) = 0 .
pad [128 - unsafe.Sizeof(poolLocalInternal{})%128]byte
}
而 poolLocalInternal 也包含兩個(gè)字段:private 和 shared。private,代表一個(gè)緩存的元素,而且只能由相應(yīng)的一個(gè) P 存取。因?yàn)橐粋€(gè) P 同時(shí)只能執(zhí)行一個(gè) goroutine,所以不會(huì)有并發(fā)的問(wèn)題。shared,可以由任意的 P 訪問(wèn),但是只有本地的 P 才能 pushHead/popHead,其它 P 可以 popTail,相當(dāng)于只有一個(gè)本地的 P 作為生產(chǎn)者(Producer),多個(gè) P 作為消費(fèi)者(Consumer),它是使用一個(gè) local-free 的 queue 列表實(shí)現(xiàn)的。
其實(shí)首先我們得明確幾個(gè)點(diǎn)存儲(chǔ)數(shù)據(jù)指針是local
當(dāng)發(fā)生GC的時(shí)候,pool是怎么運(yùn)行的:實(shí)際上會(huì)調(diào)用一個(gè)函數(shù)叫做poolcleanup
1.poolCleanup方法
func poolCleanup() {
// This function is called with the world stopped, at the beginning of a garbage collection.
// It must not allocate and probably should not call any runtime functions.
// Because the world is stopped, no pool user can be in a
// pinned section (in effect, this has all Ps pinned).
// Drop victim caches from all pools.
for _, p := range oldPools {
p.victim = nil
p.victimSize = 0
}
// Move primary cache to victim cache.
for _, p := range allPools {
p.victim = p.local
p.victimSize = p.localSize
p.local = nil
p.localSize = 0
}
// The pools with non-empty primary caches now have non-empty
// victim caches and no pools have primary caches.
oldPools, allPools = allPools, nil
}
整體大概意思是:將victim的數(shù)據(jù)給清空,將local池中的數(shù)據(jù)丟給victim,
2.get方法
// Get selects an arbitrary item from the Pool, removes it from the
// Pool, and returns it to the caller.
// Get may choose to ignore the pool and treat it as empty.
// Callers should not assume any relation between values passed to Put and
// the values returned by Get.
//
// If Get would otherwise return nil and p.New is non-nil, Get returns
// the result of calling p.New.
func (p *Pool) Get() interface{} {
if race.Enabled {
race.Disable()
}
l, pid := p.pin() //將goroutine 綁定到P上
x := l.private
l.private = nil
if x == nil {
// Try to pop the head of the local shard. We prefer
// the head over the tail for temporal locality of
// reuse.
x, _ = l.shared.popHead()
if x == nil {
x = p.getSlow(pid)
}
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
if x != nil {
race.Acquire(poolRaceAddr(x))
}
}
if x == nil && p.New != nil {
x = p.New()
}
return x
}
其實(shí)就四種可能
1.從本地的private取出來(lái)x
2.當(dāng)?shù)谝徊饺〔怀龅臅r(shí)候,從本地的分片頭部取一個(gè)出來(lái)
3.當(dāng)本地分片沒(méi)有了,走慢方法getslow
4.當(dāng)大家都沒(méi)有了,生成一個(gè)新的
3.put方法
// Put adds x to the pool.
func (p *Pool) Put(x interface{}) {
if x == nil {
return
}
if race.Enabled {
if fastrand()%4 == 0 {
// Randomly drop x on floor.
return
}
race.ReleaseMerge(poolRaceAddr(x))
race.Disable()
}
l, _ := p.pin()
if l.private == nil {
l.private = x
x = nil
}
if x != nil {
l.shared.pushHead(x)
}
runtime_procUnpin()
if race.Enabled {
race.Enable()
}
}
這個(gè)要比get方法簡(jiǎn)單一些
1.如果放入的東西是nil ,return
2.如果private !=nil ,直接放到private中
3.從頭部放入本地分片中
3.getslow方法
func (p *Pool) getSlow(pid int) interface{} {
// See the comment in pin regarding ordering of the loads.
size := atomic.LoadUintptr(&p.localSize) // load-acquire
locals := p.local // load-consume
// Try to steal one element from other procs.
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i+1)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Try the victim cache. We do this after attempting to steal
// from all primary caches because we want objects in the
// victim cache to age out if at all possible.
size = atomic.LoadUintptr(&p.victimSize)
if uintptr(pid) >= size {
return nil
}
locals = p.victim
l := indexLocal(locals, pid)
if x := l.private; x != nil {
l.private = nil
return x
}
for i := 0; i < int(size); i++ {
l := indexLocal(locals, (pid+i)%int(size))
if x, _ := l.shared.popTail(); x != nil {
return x
}
}
// Mark the victim cache as empty for future gets don't bother
// with it.
atomic.StoreUintptr(&p.victimSize, 0)
return nil
}
這里其實(shí)是一個(gè)內(nèi)置函數(shù),我就不做過(guò)多講解,其實(shí)就是:當(dāng)本地分片沒(méi)有了資源了后,嘗試去竊取一個(gè)資源,竊取不到的時(shí)候我們,就會(huì)從victim的內(nèi)容中去獲取一個(gè)對(duì)象,也就是垃圾分揀站獲取一個(gè)舊的對(duì)象,但是他們的注釋其實(shí)還是很有意思的:
// Try the victim cache. We do this after attempting to steal
// from all primary caches because we want objects in the
// victim cache to age out if at all possible.
我們想盡可能的不去用這個(gè)·victim對(duì)象,至于原因我目前還不太了解。
踩坑點(diǎn)
1.內(nèi)存泄露
var buffers = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func GetBuffer() *bytes.Buffer {
return buffers.Get().(*bytes.Buffer)
}
func PutBuffer(buf *bytes.Buffer) {
buf.Reset()
buffers.Put(buf)
}
這段代碼其實(shí)看上去人畜無(wú)害,但是實(shí)際上在我們應(yīng)用中,buf底層是一個(gè)包含切片的結(jié)構(gòu)體,當(dāng)我們將這個(gè)切片擴(kuò)展到一定長(zhǎng)度后歸還,僅僅是將len重置了,實(shí)際上的cap會(huì)保留,那么我們知道切片的底層就是array,那么這個(gè)內(nèi)存我們一直回收不了就造成了泄露。
2.內(nèi)存浪費(fèi)
除了內(nèi)存泄漏以外,還有一種浪費(fèi)的情況,就是池子中的 buffer 都比較大,但在實(shí)際使用的時(shí)候,很多時(shí)候只需要一個(gè)小的 buffer,這也是一種浪費(fèi)現(xiàn)象。其實(shí),我們可以將 buffer 池分成幾層。首先,小于 512 byte 的元素的 buffer 占一個(gè)池子;其次,小于 1K byte 大小的元素占一個(gè)池子;再次,小于 4K byte 大小的元素占一個(gè)池子。這樣分成幾個(gè)池子以后,就可以根據(jù)需要,到所需大小的池子中獲取 buffer 了。