Golang-逃逸分析理解

逃逸分析(Escape analysis)是Golang中一个十分重要的概念。
Golang会在两个地方为变量分配内存,一个是全局的堆(heap)空间用来动态分配内存,另一个是goroutine的栈(stack)空间,由于Golang的内存管理是自动的,开发者并不需要关心内存在堆或栈上分配,但从性能角度出发,在栈或堆上分配内存性能差异十分巨大


逃逸分析指由编译器决定内存分配的位置
    *分配在栈(Stack)中,则函数结束后可自动将内存回收
    *分配在堆(Heap)中,则函数执行结束可交给GC(垃圾回收)处理

程序的执行效率与上述两种分配规则关联紧密
传值与传指针的主要区别在于底层值是否需要拷贝,传指针看似不涉及值的拷贝,效率会更高,但是实际情况是传递指针会涉及到变量逃逸到堆上,同时增加GC的负担

Golang在栈上的开销和回收内存的开销很低,只需要PUSH和POP两个指令,消耗的仅是将数据拷贝至内存的时间

而在堆上分配内存,很大的额外开销就是垃圾回收(GC),Golang使用的垃圾回收机制是标记清楚算法,同时在此基础上使用三色标记法和写屏障技术,以提高效率。

标记清除收集器是跟踪式垃圾收集器,其执行过程可以分成标记(Mark)和清除(Sweep)两个阶段
* 标记阶段-从根对象出发查找并标记堆中所有的存活对象
* 清除阶段-遍历堆中的全部对象,回收未被标记的垃圾对象并将回收的内存加入空闲链表
标记清楚算法的一个典型耗时是在标记期间,需要暂停程序,标记结束之后,用户程序才可以继续执行

逃逸分析不是直接的优化手段,而是通过动态分析对象的作用域,为其它优化手段提供依据的分析技术,逃逸分析是一种确定指针动态范围的静态分析,可以分析在程序的哪些地方可以访问到指针

逃逸类型
*方法逃逸(对象跳出当前方法)
    当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其它地方
*线程逃逸(对象逃出当前线程)
    这个对象甚至可能被其它线程访问到,例如赋值给类变量或可以在其它线程中访问的实例变量

变量逃逸情况:

  • 指针逃逸

    • 在函数中创建了一个对象,返回该对象的指针,在该情况下,函数虽然退出,但因为指针的存在,对象的内存不能随着函数结束而回收,只能分配在堆上
  • 栈空间不足逃逸

    • 操作系统对内核线程使用的栈空间有大小限制,由于栈空间通常较小,当递归函数实现不当时,容易导致栈溢出。对于Golang,运行时(runtime)尝试在goroutine需要的时候动态地分配栈空间,goroutine的初始栈大小为2KB,当goroutine被调度时,会绑定内核线程执行,栈空间大小也不会超过操作系统的限制,超过一定大小的局部变量将逃逸到堆上
  • 动态类型逃逸,函数参数为interface类型

    • 空接口即interface{}可以表示任意类型,当函数参数为interface{},编辑期间难以确定其参数的具体类型,会发生逃逸
  • 闭包引用对象逃逸,其实本质还是共享栈上的值

    • 函数的返回值是一个闭包函数,闭包函数访问外部变量,则外部变量将会一直存在,直到闭包函数销毁,外部变量不能随着函数的退出而回收,逃逸至堆上
    编译时可以借助选项 -gcflags=-m,查看变量逃逸的情况

    可以使用ulimit -a查看机器上栈允许占用的内存的大小

编译器可以通过逃逸分析对代码做如下优化:
(Wekipedia)

  • 同步省略或锁消除(Synchronization Elimination),如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步
  • 将堆分配转化为栈分配(Stack Allocation),如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配
  • 分离对象或标量替换(Scalar Replacement),有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中

逃逸分析结论

  • 栈上分配内存比在堆中分配内存有更高的效率
  • 栈上分配内存不需要GC处理,函数执行后自动回收
  • 堆上分配的内存使用完毕后会交给GC处理
  • 发生逃逸时,会把栈上申请的内存移动到堆上
  • 指针可以减少底层值的拷贝,提高效率,但是会产生逃逸,如果拷贝的数据量小,逃逸造成的负担(堆内存分配+GC回收)会降低效率
  • 选择值传递或指针传递关键在于要以变量的大小作为分析指标

    • 一般情况下,对于需要修改原对象值,或占用内存比较大的结构体,选择传指针。对于只读的占用内存较小的结构体,直接传值能够获得更好的性能

你可能感兴趣的:(go测试开发)