专栏首页golang小白成长记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

其中主要看中间一小段

./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 也是一个 int
  • noescape()runtime 包中使用 unsafe.Pointer 的地方被大量使用。如果作者清楚被 unsafe.Pointer 引用的数据肯定不会被逃逸,但编译器却不知道的情况下,这是很有用的。
  • 面试中秀一秀是可以的,如果在实际项目中如果使用这种unsafe包大概率会被同事打死。不建议使用! 毕竟包的名字就叫做 unsafe, 而且源码中的注释也写明了 USE CAREFULLY!

本文分享自微信公众号 - golang小白成长记(golangxbczj),作者:胖虎

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-02-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Golang内存逃逸是什么?怎么避免内存逃逸?

    C/C++中动态分配的内存需要我们手动释放,导致猿们平时在写程序时,如履薄冰。这样做有他的好处:程序员可以完全掌控内存。但是缺点也是很多的:经常出现忘记释放内存...

    sunsky
  • Golang中的逃逸分析 顶

    在Golang中,一个对象最终是分配到堆还是栈呢,接下来我们就一起通过逃逸分析来一起学习学习。

    BGBiao
  • 高频golang面试题:简单聊聊内存逃逸?

    golang程序变量会携带有一组校验数据,用来证明它的整个生命周期是否在运行时完全可知。如果变量通过了这些校验,它就可以在栈上分配。否则就说它 逃逸 了,必须在...

    9号同学
  • Golang之变量去哪儿

    写过C/C++的同学都知道,调用著名的malloc和new函数可以在堆上分配一块内存,这块内存的使用和销毁的责任都在程序员。一不小心,就会发生内存泄露,搞得胆战...

    李海彬
  • Golang之变量去哪儿

    写过C/C++的同学都知道,调用著名的malloc和new函数可以在堆上分配一块内存,这块内存的使用和销毁的责任都在程序员。一不小心,就会发生内存泄露,搞得胆战...

    梦醒人间
  • Golang逃逸分析

    介绍逃逸分析的概念,go怎么开启逃逸分析的log。 以下资料来自互联网,有错误之处,请一定告之。 sheepbao 2017.06.10

    sunsky
  • Golang 语言的内存管理

    使用 len() 获取字符串长度,返回的是字节长度,如果想要获取 unicode 长度,需要使用 utf8 包的方法。

    frank.
  • 通过实例理解 Go 逃逸分析

    本文转载自白明老师,这是中文社区里面最好、最全面的一篇关于逃逸分析的文章,写得非常好。既有理论、又有实践,引经据典,精彩至及。

    梦醒人间
  • 技术干货 | 理解 Go 内存分配

    如果你也是个 Go 开发者,你是否关心过内存的分配和回收呢?创建的对象究竟需要由 GC 进行回收,还是随着调用栈被弹出,就消失了呢? GC 导致的 Stop T...

    腾讯智能钛AI开发者
  • 走进Golang之运行与Plan9汇编

    通过上一篇走进Golang之汇编原理,我们知道了目标代码的生成经历了那些过程。今天我们一起来学习一下生成的目标代码如何在计算机上执行。以及通过查阅 Golang...

    大愚
  • Go内存管理之代码的逃逸分析

    基本上,每种编程语言都有其自己的内存模型。每个变量,常量都存储在内存的某个物理位置上,这些存储位置通过内存指针访问。

    KevinYan
  • golang range遍历的问题

      RouterSwapper是一个路由器的包装,在Use方法中,我通过将加载的中间件存储到middlewares的数组中,然后在Swap方法中,给新的Rout...

    inuyasha
  • Golang逃逸分析

    介绍逃逸分析的概念,go怎么开启逃逸分析的log。 以下资料来自互联网,有错误之处,请一定告之。 sheepbao 2017.06.10

    李海彬
  • Java 引用逃逸那些事

    为了对线上程序的性能进行优化分析, 最近在看广受推荐的《深入理解Java虚拟机》,整本书的内容不少, 目前只是根据自己所需的进行阅读, 在后续读完整本内容配合笔...

    闻人的技术博客
  • 啥?小胖连 JVM 对锁做了那些优化都不知道?真的菜!

    来到多线程的第十五篇,对前十四篇感兴趣的请点文末底部的上、下一篇标签。这篇来聊聊 JVM 对 synchronized 做了那些优化?

    一个优秀的废人
  • golang面试

    Michel_Rolle
  • Java里一些不高深但是不接触的概念

    首先要搞明白什么叫标量。所谓标量就是不可以进一步分解的量,如java中基本数据类型和reference类型,相对的一个数据可以继续分解,称为聚合量。因此 如果...

    香菜聊游戏
  • 4.2 synchronized补充

    对于一个方法而言, 里面加了三把锁, 这样是没有任何意义的. 所以可以将其进行粗化处理

    用户7798898
  • JVM之堆

    一个进程对应一个jvm实例,同时包含多个线程,这些线==程共享方法区和堆==,每个==线程独有程序计数器、本地方法栈和虚拟机栈==。

    挖掘开源的价值

扫码关注云+社区

领取腾讯云代金券