今天是个好天气 logo 今天是个好天气
  • Home
  • Go
  • MySQL
  • Redis
  • LeetCode
  • Hello World
↩️README 配个环境 基础语法 并发编程 脚手架使用 RPC 数据结构 逃逸分析

原理分析

程序初始化顺序

  • 包之间的init函数,会从导入最深层包开始初始化,层层递归最后到mian包
  • 每个包内部的初始化程序,从包变量初始化,再执行init函数
  • 多个init函数按照文件名顺序逐个初始化
  • 不管包被导入多少次,包内的init函数只会执行一次
  • 所有初始化操作完成后,才会执行main函数

逃逸分析

前情提要

  • 堆内存采用的是对一段连续内存的线性分配
  • Go语言源代码对「栈内存」和「堆内存」的分配、释放等操作,都是对虚拟内存的操作,最终中央处理器CPU会统一通过MMU(管理单元内存Memory Management Unit)转化为实际的物理内存。

什么是逃逸

  • 函数运行在栈上,栈上声明临时变量分配内存,在函数运行完毕后,回收内存(栈自动回收),每个函数的栈空间都是独立空间,其他函数无法进行访问。

  • 但是在某些情况下,需要栈上面的数据在函数结束后仍然可以被访问,这就涉及到了逃逸。

  • 数据从栈上逃逸,会跑到堆上面。栈上分配内存,需要找到一块大小合适的内存,之后通过GC(Garbage Collection)回收才能释放。

  • Go实现了gc垃圾回收机制,但gc会影响程序运行性能,所以要尽量减少程序的gc操作.

逃逸分析过程

逃逸分析原则:如果一个函数返回一个对变量的引用,就会发生逃逸。

编译器会分析代码的特征和生命周期。Go中的变量只有在编译器证明函数返回不会被再引用,才会分配到栈上,否则其他情况都是分配到堆上。

简单来说,编译器会根据变量是否被外部引用来决定是否逃逸:

  • 如果函数外部没有引用,则优先放到栈中:
  • 如果函数外部存在引用,则必定放到堆中;

指针逃逸

传递指针可以减少底层值的拷贝,可以提高效率,但是如果拷贝的数据量小, 由于指针传递会产生逃逸,可能会使用堆,也可能会增加GC的负担,所以传递指针不一 定是高效的。

package main

type Student struct{
    Name string
    Age int
}

func StudentRegister(name string, age int) *Student{
    s := new(Student)
    s.Name = name
    s.Age = age
    return s
}

func main(){
    StudentRegister("jim", 18)
}
  • s本身为一个指针,作为函数返回值返回,会发生逃逸。

动态类型逃逸

很多函数都是interface类型,编译期间很难确定参数的具体类型,也能产生逃逸。

常见情况

  • 在方法内把局部变量指针返回,被外部引用,其生命周期大于栈,则溢出。
  • 发送指针或带有指针的值到channel,因为编译时候无法知道那个goroutine会在channel接受数据,编译器无法知道什么时候释放。
  • 在一个切片上存储指针或带指针的值。比如[]*string,导致切片内容逃逸,其引用值一直在堆上。
  • 因为切片的append导致超出容量,切片重新分配地址,切片背后的存储基于运行时的数据进行扩充,就会在堆上分配。
  • 在interface类型上调用方法,在Interface调用方法是动态调度的,只有在运行时才知道。

如何避免

  • go语言的接口类型方法调用是动态,因此不能在编译阶段确定,所有类型结构转换成接口的过程会涉及到内存逃逸发生,在频次访问较高的函数尽量避免调用接口。
  • 不要盲目使用变量指针作为参数,虽然减少了复制,但变量逃逸的开销更大。
  • 设定合理的slice长度,避免频繁超出容量,重新分配。
  • 如果切片在编译期间的大小不能够确认或者大小超出栈的限制,多数情况下都会分配到堆上。

总结

  • 堆上动态分配内存比栈上静态分配内存,开销大很多。
  • 变量分配在栈上需要能在编译期确定它的作用域,否则会分配到堆上。
  • Go编译器会在编译期对考察变量的作用域,并作一系列检查,如果它的作用域在运行期间对编译器一直是可知的,那么就会分配到栈上。简单来说,编译器会根据变量是否被外部引用来决定是否逃逸。
  • 对于Go程序员来说,编译器的这些逃逸分析规则不需要掌握,我们只需通过go build-gcflags'-m'命令来观察变量逃逸情况就行了
  • 不要盲目使用变量的指针作为函数参数,虽然它会减少复制操作。但其实当参数为变 量自身的时候,复制是在栈上完成的操作,开销远比变量逃逸后动态地在堆上分配内 存少的多。
  1. 程序初始化顺序
  2. 逃逸分析
    1. 前情提要
    2. 什么是逃逸
    3. 逃逸分析过程
    4. 指针逃逸
    5. 动态类型逃逸
    6. 常见情况
    7. 如何避免
    8. 总结
Created by shixiaocaia | Powered by idoc
Think less and do more.