1. 程式人生 > >換人!golang面試官:連怎麼避免記憶體逃逸都不知道?

換人!golang面試官:連怎麼避免記憶體逃逸都不知道?

![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWdrci5jbi1iai51ZmlsZW9zLmNvbS9jZDJkODUxZS1hZWQ1LTRlNjYtOGFmNy0wMjczZDc0NDgzNzAucG5n?x-oss-process=image/format,png) ## 問題 怎麼避免**記憶體逃逸**? ## 怎麼答 在```runtime/stubs.go:133```有個函式叫```noescape```。```noescape```可以在逃逸分析中**隱藏一個指標**。讓這個指標在逃逸分析中**不會被檢測為逃逸**。 ```go // noescape hides a pointer from escape analysis. noescape is // the identity function but escape analysis doesn't think the // output depends on the input. noescape is inlined and currently // compiles down to zero instructions. // USE CAREFULLY! //go:nosplit func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } ``` ## 舉例 - 通過一個例子加深理解,接下來嘗試下怎麼通過 ```go build -gcflags=-m``` 檢視逃逸的情況。 ```go package main import ( "unsafe" ) type A struct { S *string } func (f *A) String() string { return *f.S } type ATrick struct { S unsafe.Pointer } func (f *ATrick) String() string { return *(*string)(f.S) } func NewA(s string) A { return A{S: &s} } func NewATrick(s string) ATrick { return ATrick{S: noescape(unsafe.Pointer(&s))} } func noescape(p unsafe.Pointer) unsafe.Pointer { x := uintptr(p) return unsafe.Pointer(x ^ 0) } func main() { s := "hello" f1 := NewA(s) f2 := NewATrick(s) s1 := f1.String() s2 := f2.String() _ = s1 + s2 } ``` 執行```go build -gcflags=-m main.go``` ```go $go build -gcflags=-m main.go # command-line-arguments ./main.go:11:6: can inline (*A).String ./main.go:19:6: can inline (*ATrick).String ./main.go:23:6: can inline NewA ./main.go:31:6: can inline noescape ./main.go:27:6: can inline NewATrick ./main.go:28:29: inlining call to noescape ./main.go:36:6: can inline main ./main.go:38:14: inlining call to NewA ./main.go:39:19: inlining call to NewATrick ./main.go:39:19: inlining call to noescape ./main.go:40:17: inlining call to (*A).String ./main.go:41:17: inlining call to (*ATrick).String /var/folders/45/qx9lfw2s2zzgvhzg3mtzkwzc0000gn/T/go-build763863171/b001/_gomod_.go:6:6: can inline init.0 ./main.go:11:7: leaking param: f to result ~r0 level=2 ./main.go:19:7: leaking param: f to result ~r0 level=2 ./main.go:24:16: &s escapes to heap ./main.go:23:13: moved to heap: s ./main.go:27:18: NewATrick s does not escape ./main.go:28:45: NewATrick &s does not escape ./main.go:31:15: noescape p does not escape ./main.go:38:14: main &s does not escape ./main.go:39:19: main &s does not escape ./main.go:40:10: main f1 does not escape ./main.go:41:10: main f2 does not escape ./main.go:42:9: main s1 + s2 does not escape ``` 其中主要看中間一小段 ```go ./main.go:24:16: &s escapes to heap //這個是NewA中的,逃逸了 ./main.go:23:13: moved to heap: s ./main.go:27:18: NewATrick s does not escape // NewATrick裡的s的卻沒逃逸 ./main.go:28:45: NewATrick &s does not escape ``` ## 解釋 - 上段程式碼對```A```和```ATrick```同樣的功能有兩種實現:他們包含一個 ```string``` ,然後用 ```String()``` 方法返回這個字串。但是從逃逸分析看```ATrick``` 版本沒有逃逸。 - ```noescape()``` 函式的作用是遮蔽輸入和輸出的依賴關係。使編譯器不認為 ```p``` 會通過 ```x``` 逃逸, 因為 ```uintptr()``` 產生的引用是編譯器無法理解的。 - 內建的 ```uintptr``` 型別是一個真正的指標型別,但是在編譯器層面,它只是一個儲存一個 ```指標地址``` 的 ```int``` 型別。程式碼的最後一行返回 ```unsafe.Pointer``` 也是一個 ```int```。 - ```noescape()``` 在 ```runtime``` 包中使用 ```unsafe.Pointer``` 的地方被大量使用。如果作者清楚被 ```unsafe.Pointer``` 引用的資料肯定不會被逃逸,但編譯器卻不知道的情況下,這是很有用的。 - **面試中秀一秀是可以的**,如果在實際專案中如果使用這種unsafe包大概率會被同事打死。**不建議使用!** 畢竟包的名字就叫做 ```unsafe```, 而且原始碼中的註釋也寫明瞭 ```USE CAREFULLY! ```。 #### 文章推薦: - [golang面試題:簡單聊聊記憶體逃逸?](https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483686&idx=1&sn=e48c51107191f02da5751a19a54f7d41&chksm=9aee288fad99a199c126d5ff735af7320356ce4bb5753ae59ac6231e596354499414b5705b79&token=2092782362&lang=zh_CN#rd) - [golang面試題:字串轉成byte陣列,會發生記憶體拷貝嗎?](https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483669&idx=1&sn=88f754ddabc04eb3f66ba8ac37ee1461&chksm=9aee28bcad99a1aa1ada41cfccaffc7ef4719a9bc11c1bef45b7d1b5427c1faa12d8d0c3156f&token=2092782362&lang=zh_CN#rd) - [golang面試題:翻轉含有`中文、數字、英文字母`的字串](https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483664&idx=1&sn=23a0cf8a78b1d9c30b2e3bc102bf421e&chksm=9aee28b9ad99a1af6c879ba4b1f6439e4c21c363f0a668f322c082ca334b62255507828f66d4&token=2092782362&lang=zh_CN#rd) - [golang面試題:拷貝大切片一定比小切片代價大嗎?](https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483674&idx=1&sn=ce4b5fee48c54ff69127ef2bd5d91427&chksm=9aee28b3ad99a1a57eed7651a16fd4bdc35ff23937e423c5e1322a234652fd135f1a16abbece&token=2092782362&lang=zh_CN#rd) - [golang面試題:能說說uintptr和unsafe.Pointer的區別嗎?](https://mp.weixin.qq.com/s?__biz=MzAwMDAxNjU4Mg==&mid=2247483679&idx=1&sn=7075859e59741b1d0a81dc472b8ce45f&chksm=9aee28b6ad99a1a0599416886660d9ea56bd7fec18841af0e5fe86c3daea3973732a83d7eabb&token=2092782362&lang=zh_CN#rd) ###### 如果你想每天學習一個知識點,關注我的【公】【眾】【號】【golang小白