golang面试题:怎么避免内存逃逸?

问题
怎么避免内存逃逸?
怎么答
在runtime/stubs.go:133有个函数叫noescape。noescape可以在逃逸分析中隐藏一个指针。让这个指针在逃逸分析中不会被检测为逃逸。
 // 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
其中主要看中间一小段
./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面试题:简单聊聊内存逃逸?
- golang面试题:字符串转成byte数组,会发生内存拷贝吗?
- golang面试题:翻转含有中文、数字、英文字母的字符串
- golang面试题:拷贝大切片一定比小切片代价大吗?
- golang面试题:能说说uintptr和unsafe.Pointer的区别吗?
如果你想每天学习一个知识点?

 投递百度等公司10个岗位
投递百度等公司10个岗位 查看13道真题和解析
查看13道真题和解析