91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

golang?pprof監控memory?block?mutex統計原理是什么

發布時間:2023-04-08 16:29:55 來源:億速云 閱讀:199 作者:iii 欄目:開發技術

這篇“golang pprof監控memory block mutex統計原理是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“golang pprof監控memory block mutex統計原理是什么”文章吧。

bucket結構體介紹

// src/runtime/mprof.go:48
type bucket struct {
	next    *bucket
	allnext *bucket
	typ     bucketType // memBucket or blockBucket (includes mutexProfile)
	hash    uintptr
	size    uintptr
	nstk    uintptr
}

挨個詳細解釋下這個bucket結構體: 首先是兩個指針,一個next 指針,一個allnext指針,allnext指針的作用就是形成一個鏈表結構,剛才提到的每次記錄分配信息時,如果新增了bucket,那么這個bucket的allnext指針將會指向 bucket的鏈表頭部。

bucket的鏈表頭部信息是由一個全局變量存儲起來的,代碼如下:

// src/runtime/mprof.go:140
var (
	mbuckets  *bucket // memory profile buckets
	bbuckets  *bucket // blocking profile buckets
	xbuckets  *bucket // mutex profile buckets
	buckhash  *[179999]*bucket

不同的指標類型擁有不同的鏈表頭部變量,mbuckets 是內存指標的鏈表頭,bbuckets 是block指標的鏈表頭,xbuckets 是mutex指標的鏈表頭。

這里還有個buckethash結構,無論那種指標類型,只要有bucket結構被創建,那么都將會在buckethash里存上一份,而buckethash用于解決hash沖突的方式則是將沖突的bucket通過指針形成鏈表聯系起來,這個指針就是剛剛提到的next指針了。

至此,解釋完了bucket的next指針,和allnext指針,我們再來看看bucket的其他屬性。

// src/runtime/mprof.go:48
type bucket struct {
	next    *bucket
	allnext *bucket
	typ     bucketType // memBucket or blockBucket (includes mutexProfile)
	hash    uintptr
	size    uintptr
	nstk    uintptr
}

type 屬性含義很明顯了,代表了bucket屬于那種指標類型。

hash 則是存儲在buckethash結構內的hash值,也是在buckethash 數組中的索引值。

size 記錄此次分配的大小,對于內存指標而言有這個值,其余指標類型這個值為0。

nstk 則是記錄此次分配時,堆棧信息數組的大小。還記得在上一講golang pprof監控系列(2) —— memory,block,mutex 使用里從網頁看到的堆棧信息嗎。

heap profile: 7: 5536 [110: 2178080] @ heap/1048576
2: 2304 [2: 2304] @ 0x100d7e0ec 0x100d7ea78 0x100d7f260 0x100d7f78c 0x100d811cc 0x100d817d4 0x100d7d6dc 0x100d7d5e4 0x100daba20
#	0x100d7e0eb	runtime.allocm+0x8b		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1881
#	0x100d7ea77	runtime.newm+0x37		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2207
#	0x100d7f25f	runtime.startm+0x11f		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2491
#	0x100d7f78b	runtime.wakep+0xab		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:2590
#	0x100d811cb	runtime.resetspinning+0x7b	/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:3222
#	0x100d817d3	runtime.schedule+0x2d3		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:3383
#	0x100d7d6db	runtime.mstart1+0xcb		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1419
#	0x100d7d5e3	runtime.mstart0+0x73		/Users/lanpangzi/goproject/src/go/src/runtime/proc.go:1367
#	0x100daba1f	runtime.mstart+0xf		/Users/lanpangzi/goproject/src/go/src/runtime/asm_arm64.s:117

nstk 就是記錄的堆棧信息數組的大小,看到這里,你可能會疑惑,這里僅僅是記錄了堆棧大小,堆棧的內容呢?關于分配信息的記錄呢?

要回答這個問題,得搞清楚創建bucket結構體的時候,內存是如何分配的。

首先要明白結構體在進行內存分配的時候是一塊連續的內存,例如剛才介紹bucket結構體的時候講到的幾個屬性都是在一塊連續的內存上,當然,指針指向的地址可以不和結構體內存連續,但是指針本身是存儲在這一塊連續內存上的。

接著,我們來看看runtime是如何創建一個bucket的。

// src/runtime/mprof.go:162
func newBucket(typ bucketType, nstk int) *bucket {
	size := unsafe.Sizeof(bucket{}) + uintptr(nstk)*unsafe.Sizeof(uintptr(0))
	switch typ {
	default:
		throw("invalid profile bucket type")
	case memProfile:
		size += unsafe.Sizeof(memRecord{})
	case blockProfile, mutexProfile:
		size += unsafe.Sizeof(blockRecord{})
	}
	b := (*bucket)(persistentalloc(size, 0, &memstats.buckhash_sys))
	bucketmem += size
	b.typ = typ
	b.nstk = uintptr(nstk)
	return b
}

上述代碼是創建一個bucket時源碼, 其中persistentalloc 是runtime內部一個用于分配內存的方法,底層還是用的mmap,這里就不展開了,只需要知道該方法可以分配一段內存,size 則是需要分配的內存大小。

persistentalloc返回后的unsafe.Pointer可以強轉為bucket類型的指針,unsafe.Pointer是go編譯器允許的 代表指向任意類型的指針 類型。所以關鍵是看 分配一個bucket結構體的時候,這個size的內存空間是如何計算出來的。

首先unsafe.Sizeof 得到分配一個bucket代碼結構 本身所需要的內存長度,然后加上了nstk 個uintptr 類型的內存長度 ,uintptr代表了一個指針類型,還記得剛剛提到nstk的作用嗎?nstk表明了堆棧信息數組的大小,而數組中每個元素就是一個uintptr類型,指向了具體的堆棧位置。

接著判斷 需要創建的bucket的類型,如果是memProfile 內存類型 則又用unsafe.Sizeof 得到一個memRecord的結構體所占用的空間大小,如果是blockProfile,或者是mutexProfile 則是在size上加上一個blockRecord結構體占用的空間大小。memRecord和blockRecord 里承載了此次內存分配或者此次阻塞行為的詳細信息。

// src/runtime/mprof.go:59
type memRecord struct {
	active memRecordCycle
	future [3]memRecordCycle
}
// src/runtime/mprof.go:120
type memRecordCycle struct {
	allocs, frees           uintptr
	alloc_bytes, free_bytes uintptr
}

關于內存分配的詳細信息最后是有memRecordCycle 承載的,里面有此次內存分配的內存大小和分配的對象個數。那memRecord 里的active 和future又有什么含義呢,為啥不干脆用memRecordCycle結構體來表示此次內存分配的詳細信息? 這里我先預留一個坑,放在下面在解釋,現在你只需要知道,在分配一個內存bucket結構體的時候,也分配了一段內存空間用于記錄關于內存分配的詳細信息。

然后再看看blockRecord。

// src/runtime/mprof.go:135
type blockRecord struct {
	count  float64
	cycles int64
}

blockRecord 就比較言簡意賅,count代表了阻塞的次數,cycles則代表此次阻塞的周期時長,關于周期的解釋可以看看我前面一篇文章golang pprof監控系列(2) —— memory,block,mutex 使用 ,簡而言之,周期時長是cpu記錄時長的一種方式。你可以把它理解成就是一段時間,不過時間單位不在是秒了,而是一個周期。

可以看到,在計算一個bucket占用的空間的時候,除了bucket結構體本身占用的空間,還預留了堆棧空間以及memRecord或者blockRecord 結構體占用的內存空間大小

你可能會疑惑,這樣子分配一個bucket結構體,那么如何取出bucket中的memRecord 或者blockRecord結構體呢? 答案是 通過計算memRecord在bucket 中的位置,然后強轉unsafe.Pointer指針。

拿memRecord舉例,

//src/runtime/mprof.go:187
func (b *bucket) mp() *memRecord {
	if b.typ != memProfile {
		throw("bad use of bucket.mp")
	}
	data := add(unsafe.Pointer(b), unsafe.Sizeof(*b)+b.nstk*unsafe.Sizeof(uintptr(0)))
	return (*memRecord)(data)
}

上面的地址可以翻譯成如下公式:

memRecord開始的地址 = bucket指針的地址 +  bucket結構體的內存占用長度 + 棧數組占用長度 

這一公式成立的前提便是 分配結構體的時候,是連續的分配了一塊內存,所以我們當然能通過bucket首部地址以及中間的空間長度計算出memRecord開始的地址。

至此,bucket的結構體描述算是介紹完了,但是還沒有深入到記錄指標信息的細節,下面我們深入研究下記錄細節,正戲開始。

記錄指標細節介紹

由于內存分配的采樣還是和block阻塞信息的采樣有點點不同,所以我還是決定分兩部分來介紹下,先來看看內存分配時,是如何記錄此次內存分配信息的。

memory

首先在上篇文章golang pprof監控系列(2) —— memory,block,mutex 使用 我介紹過 MemProfileRate ,MemProfileRate 用于控制內存分配的采樣頻率,代表平均每分配MemProfileRate字節便會記錄一次內存分配記錄。

當觸發記錄條件時,runtime便會調用 mProf_Malloc 對此次內存分配進行記錄,

// src/runtime/mprof.go:340
func mProf_Malloc(p unsafe.Pointer, size uintptr) {
	var stk [maxStack]uintptr
	nstk := callers(4, stk[:])
	lock(&proflock)
	b := stkbucket(memProfile, size, stk[:nstk], true)
	c := mProf.cycle
	mp := b.mp()
	mpc := &mp.future[(c+2)%uint32(len(mp.future))]
	mpc.allocs++
	mpc.alloc_bytes += size
	unlock(&proflock)
	systemstack(func() {
		setprofilebucket(p, b)
	})
}

實際記錄之前還會先獲取堆棧信息,上述代碼中stk 則是記錄堆棧的數組,然后通過 stkbucket 去獲取此次分配的bucket,stkbucket 里會判斷是否先前存在一個相同bucket,如果存在則直接返回。而判斷是否存在相同bucket則是看存量的bucket的分配的內存大小和堆棧位置是否和當前一致。

// src/runtime/mprof.go:229
for b := buckhash[i]; b != nil; b = b.next {
		if b.typ == typ && b.hash == h && b.size == size && eqslice(b.stk(), stk) {
			return b
		}
	}

通過剛剛介紹bucket結構體,可以知道 buckhash 里容納了程序中所有的bucket,通過一段邏輯算出在bucket的索引值,也就是i的值,然后取出buckhash對應索引的鏈表,循環查找是否有相同bucket。相同則直接返回,不再創建新bucket。

讓我們再回到記錄內存分配的主邏輯,stkbucket 方法創建或者獲取 一個bucket之后,會通過mp()方法獲取到其內部的memRecord結構,然后將此次的內存分配的字節累加到memRecord結構中。

不過這里并不是直接由memRecord 承載累加任務,而是memRecord的memRecordCycle 結構體。

c := mProf.cycle
	mp := b.mp()
	mpc := &mp.future[(c+2)%uint32(len(mp.future))]
	mpc.allocs++
	mpc.alloc_bytes += size

這里先是從memRecord 結構體的future結構中取出一個memRecordCycle,然后在memRecordCycle上進行累加字節數,累加分配次數。

這里有必要介紹下mProf.cycle 和memRecord中的active和future的作用了。

我們知道內存分配是一個持續性的過程,內存的回收是由gc定時執行的,golang設計者認為,如果每次產生內存分配的行為就記錄一次內存分配信息,那么很有可能這次分配的內存雖然程序已經沒有在引用了,但是由于還沒有垃圾回收,所以會造成內存分配的曲線就會出現嚴重的傾斜(因為內存只有垃圾回收以后才會被記錄為釋放,也就是memRecordCycle中的free_bytes 才會增加,所以內存分配曲線會在gc前不斷增大,gc后出現陡降)。

所以,在記錄內存分配信息的時候,是將當前的內存分配信息經過一輪gc后才記錄下來,mProf.cycle 則是當前gc的周期數,每次gc時會加1,在記錄內存分配時,將當前周期數加2與future取模后的索引值記錄到future ,而在釋放內存時,則將 當前周期數加1與future取模后的索引值記錄到future,想想這里為啥要加1才能取到 對應的memRecordCycle呢? 因為當前的周期數比起內存分配的周期數已經加1了,所以釋放時只加1就好。

// src/runtime/mprof.go:362
func mProf_Free(b *bucket, size uintptr) {
	lock(&proflock)
	c := mProf.cycle
	mp := b.mp()
	mpc := &mp.future[(c+1)%uint32(len(mp.future))]
	mpc.frees++
	mpc.free_bytes += size
	unlock(&proflock)
}

在記錄內存分配時,只會往future數組里記錄,那讀取內存分配信息的 數據時,怎么讀取呢?

還記得memRecord 里有一個類型為memRecordCycle 的active屬性嗎,在讀取的時候,runtime會調用 mProf_FlushLocked()方法,將當前周期的future數據讀取到active里。

// src/runtime/mprof.go:59
type memRecord struct {
	active memRecordCycle
	future [3]memRecordCycle
}
// src/runtime/mprof.go:120
type memRecordCycle struct {
	allocs, frees           uintptr
	alloc_bytes, free_bytes uintptr
}
// src/runtime/mprof.go:305
func mProf_FlushLocked() {
	c := mProf.cycle
	for b := mbuckets; b != nil; b = b.allnext {
		mp := b.mp()
		// Flush cycle C into the published profile and clear
		// it for reuse.
		mpc := &mp.future[c%uint32(len(mp.future))]
		mp.active.add(mpc)
		*mpc = memRecordCycle{}
	}
}

代碼比較容易理解,mProf.cycle獲取到了當前gc周期,然后用當前周期從future里取出 當前gc周期的內存分配信息 賦值給acitve ,對每個內存bucket都進行這樣的賦值。

賦值完后,后續讀取當前內存分配信息時就只讀active里的數據了,至此,算是講完了runtime是如何對內存指標進行統計的。

接著,我們來看看如何對block和mutex指標進行統計的。

block mutex

block和mutex的統計是由同一個方法,saveblockevent 進行記錄的,不過方法內部針對這兩種類型還是做了一點點不同的處理。

有必要注意再提一下,mutex是在解鎖unlock時才會記錄一次阻塞行為,而block在記錄mutex鎖阻塞信息時,是在開始執行lock調用的時候記錄的 ,除此以外,block在select 阻塞,channel通道阻塞,wait group 產生阻塞時也會記錄一次阻塞行為。

// src/runtime/mprof.go:417
func saveblockevent(cycles, rate int64, skip int, which bucketType) {
	gp := getg()
	var nstk int
	var stk [maxStack]uintptr
	if gp.m.curg == nil || gp.m.curg == gp {
		nstk = callers(skip, stk[:])
	} else {
		nstk = gcallers(gp.m.curg, skip, stk[:])
	}
	lock(&proflock)
	b := stkbucket(which, 0, stk[:nstk], true)
	if which == blockProfile && cycles < rate {
		// Remove sampling bias, see discussion on http://golang.org/cl/299991.
		b.bp().count += float64(rate) / float64(cycles)
		b.bp().cycles += rate
	} else {
		b.bp().count++
		b.bp().cycles += cycles
	}
	unlock(&proflock)
}

首先還是獲取堆棧信息,然后stkbucket() 方法獲取到 一個bucket結構體,然后bp()方法獲取了bucket里的blockRecord 結構,并對其count次數和cycles阻塞周期時長進行累加。

// src/runtime/mprof.go:135
type blockRecord struct {
	count  float64
	cycles int64
}

注意針對blockProfile 類型的次數累加 還進行了特別的處理,還記得上一篇golang pprof監控系列(2) &mdash;&mdash; memory,block,mutex 使用提到的BlockProfileRate參數嗎,它是用來設置block采樣的納秒采樣率的,如果阻塞周期時長cycles小于BlockProfileRate的話,則需要fastrand函數乘以設置的納秒時間BlockProfileRate 來決定是否采樣了,所以如果是小于BlockProfileRate 并且saveblockevent進行了記錄阻塞信息的話,說明我們只是采樣了部分這樣情況的阻塞,所以次數用BlockProfileRate 除以 此次阻塞周期時長數,得到一個估算的總的 這類阻塞的次數。

讀取阻塞信息就很簡單了,直接讀取阻塞bucket的count和周期數即可。

以上就是關于“golang pprof監控memory block mutex統計原理是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

大悟县| 晋中市| 汉沽区| 砚山县| 桑日县| 临泽县| 江陵县| 武义县| 买车| 钟山县| 定南县| 司法| 澄江县| 聊城市| 博客| 东光县| 广元市| 定襄县| 利川市| 六盘水市| 崇礼县| 武隆县| 九寨沟县| 锡林浩特市| 三穗县| 永宁县| 涞水县| 秦安县| 刚察县| 西贡区| 钦州市| 广德县| 抚顺县| 来宾市| 龙泉市| 桃园县| 灵山县| 永济市| 临沧市| 榆树市| 伊宁县|