• Go语言逃逸分析全纪录


    目录

    什么是逃逸分析?

    逃逸分析是为了什么?

    如何查看逃逸

    哪些情况会出现逃逸

    1,指针逃逸

    2,栈空间不足逃逸

    3,动态分配逃逸

    4,闭包引用对象逃逸


    什么是逃逸分析?

    Go程序为变量分配内存分为两途径:

    1. 1,全局的堆空间动态分配内存
    2. 2,每个goroutine的栈空间

    一般来说开发者并不需要关心内存分配在栈上 or 还是堆上。但从性能的角度出发,在栈上分配内存和在堆上分配内存,性能差异还是非常大的。


    在栈上分配和回收内存的开销很低,只需要2个CPU 指令:PUSH、POP。

    前者是将数据push到栈空间以完成分配,后者则是释放空间,也就是说在栈上分配内存,消耗的仅是将数据拷贝到内存的时间,而在堆上分配,一个很大的额外开销则是垃圾回收。

    在Go中,堆内存通过垃圾回收机制自动管理,Go的垃圾回收采用的是标记清除算法,并且在此基础上使用了三色标记法和写屏障技术,提高了效率。

    标记清除算法的一个典型操作是在标记期间,需要STW,即暂停程序(Stop the world),标记结束之后,用户程序才可以继续执行。


    堆内存分配导致垃圾回收的开销远大于栈空间分配与释放的开销。


    那么 Go编译器怎么知道某个变量需要分配在栈 or 堆上呢?
    编译器决定内存分配位置的方式,就称之为逃逸分析(escape analysis)。逃逸分析由编译器完成,作用于编译阶段。


    逃逸分析是为了什么?

    go在编译的时候会进行逃逸分析,目的是决定一个对象放栈上还是堆上,不逃逸的对象放栈上,可能逃逸的放堆上。
    最大的好处是减少gc的压力,不逃逸的对象分配在栈上,当函数返回时就回收了资源,不需要gc标记清除。
    因为通过逃逸分析就可以确定哪些变量可以分配在栈上,栈的分配比堆快,性能好。


    如何查看逃逸

    编译go代码时加

    -gcflags "-m -l"

    参数即可。

    哪些情况会出现逃逸

    1,指针逃逸

    1. func f(x, y int) *int {
    2.     n := new(int) // new(int) escapes to heap
    3.     *n = x * y
    4.     return n
    5. }
    6.     _ = f(10, 20)

    上述代码中在函数结束时返回一个指针,此时进行编译,new(int)就会导致变量n逃逸到堆上。
    此时n作为函数f的返回值会在main中继续使用,因此n指向的内存不能分配在栈上,会随着函数结束而被回收。

    2,栈空间不足逃逸

    1. _ = make([]int, 1000, 8191) // make([]int, 1000, 8191) does not escape 此时<64KB (int占用8字节)
    2. _ = make([]int, 1000, 8193) // make([]int, 1000, 8193) escapes to heap 此时>64KB
    3. _ = make([]int, 8193) // make([]int, 8193) escapes to heap
    4. _ = make([]int, 1000, 10000) // make([]int, 1000, 10000) escapes to heap

    也就是说,你每次make时创建不同的长度,其实编译器都会做不同的处理,不同情况可能会导致开销变大;
    当切片占用内存超过一定大小 或无法确定当前切片长度时,其内存将在堆上分配。

    其达到多少会逃逸到堆上受操作系统对内核线程栈空间的大小限制。

    3,动态分配逃逸

    1. func f1() {
    2. s := 10
    3. _ = make([]int, s) // make([]int, s) escapes to heap
    4. }
    5. f1() // 因为变量s可能被更改,所以编译器认为应该分配到 heap

    其它情况比如动态类型,入参为interface{},编译器无法确定到底是什么类型,也会发生逃逸。

    就像fmt.Printf(),它的障眼法可不少。

    4,闭包引用对象逃逸


    Go语言支持闭包机制,如下:

    1. func f2() func() int {
    2.     a, b := 1, 2
    3.     return func() int { // func literal escapes to heap
    4.         return a + b
    5.     }
    6. }
    7. f2()

    本来a和b作为函数局部变量应该分配到stack中,但是由于f()函数返回了一个闭包函数,该闭包函数访问了外部变量a和b, 此时若函数f2 return,实际a和b还是被引用,无法回收,因此编译器认为a和b应该分配到堆上。

  • 相关阅读:
    Pytorch CIFAR10图像分类 Vision Transformer(ViT) 篇
    typescript83-属性的默认值
    Java多线程(四)锁策略(CAS,死锁)和多线程对集合类的使用
    链表经典面试题(一)
    Oracle/PLSQL: To_DSInterval Function
    MDM数据清洗功能开发说明
    seata-分布式事务
    Jmeter+Ant+Git/SVN+Jenkins实现持续集成接口测试,一文精通(三)
    分布式存储--类Redis存储
    Eclipse里 ant 打包 BUILD FAILED 总结
  • 原文地址:https://blog.csdn.net/HYZX_9987/article/details/125447807