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

問題

怎麼避免內存逃逸golang

怎麼答

runtime/stubs.go:133有個函數叫noescapenoescape能夠在逃逸分析中隱藏一個指針。讓這個指針在逃逸分析中不會被檢測爲逃逸面試

// 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 查看逃逸的狀況。
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 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

其中主要看中間一小段ide

./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

解釋

  • 上段代碼對AATrick一樣的功能有兩種實現:他們包含一個 string ,而後用 String() 方法返回這個字符串。可是從逃逸分析看ATrick 版本沒有逃逸。函數

  • noescape() 函數的做用是遮蔽輸入和輸出的依賴關係。使編譯器不認爲 p 會經過 x 逃逸, 由於 uintptr() 產生的引用是編譯器沒法理解的。學習

  • 內置的 uintptr 類型是一個真正的指針類型,可是在編譯器層面,它只是一個存儲一個 指針地址int 類型。代碼的最後一行返回 unsafe.Pointer 也是一個 intui

  • noescape()runtime 包中使用 unsafe.Pointer 的地方被大量使用。若是做者清楚被 unsafe.Pointer 引用的數據確定不會被逃逸,但編譯器殊不知道的狀況下,這是頗有用的。3d

  • 面試中秀一秀是能夠的,若是在實際項目中若是使用這種unsafe包大機率會被同事打死。不建議使用! 畢竟包的名字就叫作 unsafe, 並且源碼中的註釋也寫明瞭 USE CAREFULLY! 指針

文章推薦:

若是你想天天學習一個知識點,關注個人【公】【衆】【號】【golang小白成長記
相關文章
相關標籤/搜索