從記憶體分配策略(堆、棧)的角度分析,函式傳遞指標真的比傳值效率高嗎?
從記憶體分配策略(堆、棧)的角度分析,函式傳遞指標真的比傳值效率高嗎?
介紹
對於初學者,肯定很多同學在糾結:
- 函式傳遞指標還是傳值?
- 兩種選擇的本質區別是什麼?
- 哪種方式的效能更高呢?
分析
要找到區別,那肯定需要下功夫,那就從 Golang 的實現機制中來分析吧。首先,在Golang 中有一個很重要的概念那就是 逃逸分析(Escape analysis),所謂的逃逸分析指由編譯器決定記憶體分配的位置。
- 分配在 棧中,則函式執行結束可自動將記憶體回收
- 分配在 堆中,則函式執行結束可交給GC(垃圾回收)處理
最終程式的執行效率和這個兩種分配規則是有這重要關聯的,而傳值和傳指標的主要區別在於底層值是否需要拷貝,表面上看傳指標不涉及值拷貝,效率肯定更高。但是實際情況是指傳針會涉及到變數逃逸到堆上,而且會增加GC的負擔,所以本文我們要做的內容就是進行 逃逸分析 ,安裝慣例先上結論。
- 棧上分配記憶體比在堆中分配記憶體有更高的效率
- 棧上分配的記憶體不需要GC處理,函式執行後自動回收
- 堆上分配的記憶體使用完畢會交給GC處理
- 發生逃逸時,會把棧上的申請的記憶體移動到堆上
- 指標可以減少底層值的拷貝,可以提高效率,但是會產生逃逸,但是如果拷貝的資料量小,逃逸造成的負擔(堆記憶體分配+GC回收)會降低效率
- 因此選擇值傳遞還是指標傳遞,變數的大小是一個很重要的分析指標
每種方式都有各自的優缺點,棧上的值,減少了 GC 的壓力,但是要維護多個副本,堆上的指標,會增加 GC 的壓力,但只需維護一個值。因此選擇哪種方式,依據自己的業務情況參考這個標準進行選擇。
先上一段程式碼分析下
// escape.go package main type person struct { name string ageint } func main() { makePerson(32, "艾瑪·斯通") showPerson(33, "楊冪") } func makePerson(age int, name string) *person { maliya := person{name, age} return &maliya } func showPerson(age int, name string) person { yangmi := person{name, age} return yangmi } 複製程式碼
執行如下命令,進行逃逸分析
go build -gcflags="-m -m -l" escape.go 複製程式碼
輸出結果:
Escape/Escape.go:15:9: &maliya escapes to heap Escape/Escape.go:15:9:from ~r2 (return) at Escape/Escape.go:15:2 Escape/Escape.go:14:2: moved to heap: maliya Escape/Escape.go:13:40: leaking param: name to result ~r2 level=-1 Escape/Escape.go:13:40:from person literal (struct literal element) at Escape/Escape.go:14:18 Escape/Escape.go:13:40:from maliya (assigned) at Escape/Escape.go:14:9 Escape/Escape.go:13:40:from &maliya (address-of) at Escape/Escape.go:15:9 Escape/Escape.go:13:40:from ~r2 (return) at Escape/Escape.go:15:2 Escape/Escape.go:18:39: leaking param: name to result ~r2 level=0 Escape/Escape.go:18:39:from person literal (struct literal element) at Escape/Escape.go:19:18 Escape/Escape.go:18:39:from yangmi (assigned) at Escape/Escape.go:19:9 Escape/Escape.go:18:39:from ~r2 (return) at Escape/Escape.go:20:2 複製程式碼
從結果中我們看到變數 &maliya 發生了逃逸,變數 yangmi 沒有逃逸
&maliya escapes to heap from ~r2 (return) at Escape/Escape.go:15:2 moved to heap: maliya 複製程式碼
所以 makePerson 返回的是指標型別,發生了逃逸,而showPerson 返回的是值型別沒有逃逸。
關於變數逃逸的情況還有很多,網上有很多分析的文章,就不一一舉例了,直接給出結論:
- 共享了棧上的一個值時,它就會逃逸
- 棧空間不足逃逸(比如建立一個超大的slice,超過棧空間)
- 動態型別逃逸,函式引數為interface型別(典型的fmt.Println方法)
- 閉包引用物件逃逸,其實本質還是共享了棧上的值