go逃逸分析

在go语言中,是由编译器调度来分配和释放调度器,栈区往往存放着函数参数、函数变量、函数调用帧,它们随着函数的创建而被分配,又随着函数的结束而销毁。

栈的特点内存空间小,分配内存速度快(由cpu指令集实现分配和释放),地址连续,能自动分配和释放空间,效率高

go语言中,一个goroutine往往维护着一个自己的栈区,这个栈区只能自己使用而不能被其他goroutine使用。一个栈通常由包含了很多栈帧,描述函数之间的调用关系

与栈不同的事,堆去的内存一般是由编辑器和工程师自己共同管理的,它需要先找到一块足够内存空间才能写入数据。

堆的特点就是空间大, 但是堆分配内存的效率低,释放由GC控制

堆栈的对比

  • 栈不需要加锁,每个goroutine都独享自己的栈空间,操作栈上的内存不要加锁
  • 堆有时需要枷锁,堆上的内存,要防止多线程冲突
  • 栈空间连续,缓存性能更好
  • 堆缓存性能差

逃逸分析

栈相对于堆性能更好,所有go编译器会尽可能的把变量分配到栈上

逃逸分析是服务于内存分配的,用来在编译期间分析一个对象是要被分析到栈上还是堆上

逃逸分析有几点原则:

  • 如果变量在函数外部有被引用,就必定被放到堆上

    • 比如说函数返回值是一个指针类型
  • 如果变量内存占用过大,就优先被分配到堆上

    • 比如声明了一个容量特点大的切片
  • 如果变量内存空间占用不确定,就优先放到堆上

    • 比如函数参数使用了interface类型,无法确定变量类型
    • 比如声明切片的时候使用变量做切片容量,编译阶段无法确定变量内存大小
  • 可以使用 go run -gcflags '-m -m -l'命令来判断

使用

基于这种特点,我们在开发中也要注意。要尽量写出可以分配在栈上的代码,同样,栈上的代码生命周其要尽可能的短。这样可以减少堆内存分配开销,减小gc压力

比如:

  1. err的处理
go 复制代码
if err := func();err != nil{}
// 而不是
err := func()
if err != nil{}
  1. 函数传参的时候,不一定非要传结构体指针,有些场景 也应该直接传结构体

    • 如果结构体比较小,传值合适,因为直接传结构体进行值拷贝,这个操作是栈上的操作,开销比传指针变量逃逸到堆上小的多

    • 如果结构体比较大,传指针合适,指针类型能比传值拷贝节省大量的内存

拓展

go和c/c++的栈堆概念

  • 在c/c++中栈和堆都是操作系统层级上的概念,是关联硬件的,是由操作系统和编译器共同决定的
  • go中的栈和堆却都是逻辑上的概念(类似数据结构)。操作系统层级上的栈都被调度器、垃圾回收、系统调用等机制消耗了。而我们代码中使用的栈和堆其实是从操作系统申请的一块堆内存模拟出来的,是一种逻辑上的概念。
  • 也因此,c/c++的栈很小。而go的'栈'可以开很大,比如go的一个协程就是一个栈,通常是2或4kb,但可以开成千上万个协程。
  • 而且go有时候为了防止内存碎片化,会对整个栈进行内存迁移,然后拷贝数据(切片扩容机制,当容量大于原数组容量,就会进行迁移拷贝)。所以指针有时候是会变的,只有指针指向的值有意义。因此,go禁止指针的算术运算
相关推荐
Reart14 小时前
Leetcode 213.打家劫舍2(内含闲谈,打劫真是技术活,好题,716)
后端·算法
触底反弹14 小时前
🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!
javascript·人工智能·后端
techdashen15 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
开发语言·后端·golang
Reart15 小时前
Leetcode 198.打家劫舍(716)
后端·算法
Java编程爱好者15 小时前
Spring6.0+Boot3.0:秒级启动、万级并发的开发新姿势
后端
YYYing.15 小时前
【C++大型项目之高性能服务器框架 (七) 】Socket与ByteArray模块
服务器·c++·后端·框架·高性能·c/c++
青山木16 小时前
一把 Redis 分布式锁,踩透四个坑:锁争抢、僵尸锁、锁过期、锁丢失
java·数据库·redis·后端
神奇小汤圆16 小时前
雪花算法ID重复了?惨痛教训:请勿轻易造轮子!
后端
Java内核笔记16 小时前
万字避坑!Spring Boot 3.x 升 4.0 最全升级指南(附 Migration Checklist,建议收藏⭐)
java·后端
从零开始的代码生活_16 小时前
C++ list 原理与实践:双向链表、迭代器与简化实现
开发语言·c++·后端·学习·算法·链表·list