溫馨提示×

溫馨提示×

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

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

如何理解golang逃逸分析

發(fā)布時間:2021-10-21 13:56:48 來源:億速云 閱讀:330 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“如何理解golang逃逸分析”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

背景

最近想要將 protobuf 變量和之前設計的數(shù)據(jù)對象整合起來,維護在內(nèi)存中,以減少內(nèi)存申請和 GC 的性能損耗。

feature or bug,gogoproto 解碼疑惑

由于 gogoproto 在 unmarshal 時不保證輸入和輸出一致,作為結(jié)果的指針變量和輸入的字節(jié)切片可能不一致(比如說,在 unmarshal slice 時沒有 reset 操作)。我們需要對這個指針變量進行重置,pb 生成文件的 reset 實現(xiàn)方法如下。

func (m *Data) Reset() { *m = Data{} }

在看到 Data{} 時我陷入了疑惑,按我的理解,這一步是需要申請內(nèi)存的。那么如此一來,我們在將某個 pb 變量拋入內(nèi)存時不可避免的還是需要申請內(nèi)存,這樣本次的研發(fā)需求好像失去了意義。

我的第一反應是,這是 gogoproto 的問題,也許官方 go proto 不是這樣的??墒侵匦律珊蟀l(fā)現(xiàn) reset 方法實現(xiàn)并沒有什么區(qū)別。只不過官方 go proto 會在 unmarshal 時主動 reset。

那么,難道一開始的方向就錯了嗎?啊頭禿。

柳暗花明又一村

不死心的我開始看各種文檔,包括 gogoproto 的各種插件,可惜并沒有找到有用的內(nèi)容。接著我又開始看官方 proto 文檔。。。

這時我發(fā)現(xiàn)了一點蛛絲馬跡。

在日常使用 protobuf 時,如果不復用舊的變量,我們一般會

  1. 聲明指針變量,data := &pb.Data{}

  2. 解碼,proto.Unmarshal(bytes, data)

顯然,第一步是需要申請內(nèi)存。而按照 go proto 的源碼,unmarshal 時的 reset 操作又會申請一次內(nèi)存,難道 Google 會允許這種性能損耗?

真的嗎,我不信。

逃逸分析入門

想的太多,不如寫個 benchmark 試一下。(小心 microbenchmark 的一些坑)

benchmark

package main

import (
	"testing"
)

type boy struct {
	name string
	age  int
}

var b1 = &boy{}
var b2 = &boy{}

func Benchmark_1(b *testing.B) {

	for i := 0; i < b.N; i++ {
		temp := &boy{}
		b1 = temp
	}
}

func Benchmark_2(b *testing.B) {

	for i := 0; i < b.N; i++ {
		temp := &boy{}
		*b1 = *temp
	}
}

func Benchmark_3(b *testing.B) {

	for i := 0; i < b.N; i++ {
		temp := &boy{}
		*b1 = *temp
		b2 = temp
	}
}

結(jié)果如下。

goos: linux
goarch: amd64
pkg: bible
Benchmark_1-4   	29142411	        42.2 ns/op	      32 B/op	       1 allocs/op
Benchmark_2-4   	1000000000	         0.711 ns/op	       0 B/op	       0 allocs/op
Benchmark_3-4   	28474614	        39.5 ns/op	      32 B/op	       1 allocs/op
PASS
ok  	bible	3.258s

結(jié)果是一目了然的,temp := &boy{} 確實沒有重復申請內(nèi)存。

編譯報告

到此,只需要逐個分析就好。首先打開編譯報告看一下。

go build -gcflags "-m -m"

var b1 = &boy{}
//go:noinline
func main() {
	temp := &boy{}
	// &boy literal escapes to heap:
	//   flow: temp = &{storage for &boy literal}:
	//     from &boy literal (spill) at ./main.go:12:10
	//     from temp := &boy literal (assign) at ./main.go:12:7
	//   flow: {heap} = temp:
	//     from b1 = temp (assign) at ./main.go:13:5
	// &boy literal escapes to heap
	b1 = temp
}

新創(chuàng)建的 &boy{} 被全局變量引用,于是逃逸到堆上,成為動態(tài)變量,無法被重復利用。

var b1 = &boy{}
//go:noinline
func main() {
	temp := &boy{}
	// &boy literal does not escape
	*b1 = *temp
}

*b1 = *temp 僅僅是賦值操作,新創(chuàng)建的 &boy{} 沒有被引用,留在棧上,后續(xù)被重復利用。

匯編分析

如何理解golang逃逸分析

“如何理解golang逃逸分析”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

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

AI