Go 语言逃逸分析:内存管理的关键

2024-05-24 23:44

本文主要是介绍Go 语言逃逸分析:内存管理的关键,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 1 逃逸分析是什么?
  • 2 逃逸分析的基本思想是什么?
  • 3 逃逸分析的分配原则是什么?
  • 4 如何进行逃逸分析?
  • 5 逃逸分析案例
    • 5.1 变量在函数外存在引用
    • 5.2 引用类型的逃逸
    • 5.3 闭包捕获变量
    • 5.4 变量占用内存较大
  • 6 变量会逃逸到堆上的原因有哪些?
  • 7 如何避免逃逸?
  • 8 逃逸分析的作用有哪些?
  • 9 学习交流

前言

在正式学习逃逸分析时,我们需要提前补充变量的声明周期的概念。


  • 栈(stack):在GO语言中,栈是调用栈(call stack) 的简称。在GO程序运行时,每一个Goroutine单独维护一个自己的栈区仅允许自己使用不能被其他Goroutine使用。一个栈通常包含许多栈帧(stack frame),它描述的是函数之间的调用关系。栈的内存是由编译器自动进行分配释放的。栈区主要存储函数参数局部变量调用函数帧,它们随函数的创建而分配随函数的退出而销毁
  • 堆(heap):与栈(stack)不同的是,堆区的内存是由编译器工程师共同负责管理分配,交给Runtime GC释放。在堆上分配内存时,必须找到一块足够大的内存来存放新的变量数据。在堆上释放内存时,垃圾回收器会扫描内存空间中不被使用的对象并释放其内存。在我们开发过程中,其实考虑内存管理,主要是考虑堆内存的管理。

变量的声明周期与变量作用域的关系

  • 全局变量:它的生命周期与程序的生命周期一致
  • 局部变量:它的生命周期是动态的,从变量创建开始,到变量不再使用结束。
  • 形参和函数的返回值:它们都是属于局部变量,在函数被调用时创建,调用结束时被销毁。

1 逃逸分析是什么?

逃逸分析(Escape Analysis)是一种重要的编译时优化技术,决定将变量分配到 堆(heap)上 还是 栈(stack)上
通过逃逸分析,编译器可以判断变量的生命周期作用范围,从而选择最合适的内存分配方式,以提高程序的性能和减少内存开销


2 逃逸分析的基本思想是什么?

[!warning]- 思考: 如何知道GO变量的生命周期是完全可知的?

  • 判断变量是值类型还是引用类型,值类型是确定的完全可知的,引用类型是不可知的,不知道是否该变量被其他函数使用。
  • 检查变量的生命周期是否是完全可知的,如果是,则在栈上分配内存。
  • 如何检查不是完全可知的,也就是我们说的逃逸,必须在堆上分配内存。

3 逃逸分析的分配原则是什么?

[!warning]- 如何确定参数类型是不确定的?

  • 变量的数据类型采用interface{},编译期无法确定其具体的参数类型,所以分配到堆中。
  • 什么样的数据类型是确定的? 比如:声明了一个确定数据类型int的变量`var num int
  • GO的逃逸分析是在编译期间完成的,编译期间无法确定的参数类型是放在堆中的。
  • 变量在函数外存在引用,则必定放在
  • 变量占用内存较大,则优先放在
  • 变量在函数外部没有引用,则优先放在中。

4 如何进行逃逸分析?

逃逸分析我们可以通过命令查看结果,-gcflags选项用于向 Go 编译器传递编译标志。这些标志可以用来启用或禁用特定的编译器功能,包括逃逸分析

  • 查看基本的逃逸分析和内联信息,适用于一般情况
go build -gcflags="-m" main.go

[!warning]+ 命令解释说明

  • -m:表示输出有关内联(inlining)逃逸分析的信息

  • 查看更详细的优化信息和逃逸分析结果,并禁用内联优化,适用于需要深入调试和分析的情况。
go build -gcflags '-m -m -l'  main.go

[!warning]+ 命令解释说明

  • -m -m:表示多次使用 -m 标志,增加详细程度,会输出更多的优化信息,包括逃逸分析和内联优化的详细信息。
  • -l:表示禁用内联优化。

5 逃逸分析案例

5.1 变量在函数外存在引用

package mainimport "fmt"func createPointer() *int {var x intreturn &x  // x 逃逸到堆上
}func main() {p := createPointer()fmt.Println(*p)
}

[!note]+ 代码解析说明

  • 函数 createPointer 返回了局部变量 x 的地址,这意味着 x 在函数返回后仍然需要存在。
  • 因此,编译器将 x 分配到堆上,并在逃逸分析的输出中提示 &x escapes to heap

5.2 引用类型的逃逸

  • 例如:切片、映射、接口等引用类型的变量,如果它们的底层数据逃逸,则这些变量也会逃逸。
func createSlice() []int {s := make([]int, 10) return s // s 逃逸到堆上 
}

5.3 闭包捕获变量

如果闭包捕获了外部变量,该变量会逃逸到堆上。

func createClosure() func() { var x int return func() { x++ // x 逃逸到堆上 }
}

5.4 变量占用内存较大

func createManySlice() []int {  var s []int  for i := 0; i < 1000; i++ {  s = make([]int, 10)  }  return s // s 逃逸到堆上  
}

6 变量会逃逸到堆上的原因有哪些?

  • 函数返回值:如果返回一个局部变量的指针或引用,该变量会逃逸到堆上。
  • 闭包捕获:如果闭包捕获了外部变量,该变量会逃逸到堆上。
  1. 长生命周期:如果变量的生命周期超出了其作用域,如通过指针或引用传递给其他函数或存储在全局变量中。

7 如何避免逃逸?

避免逃逸,也就是说减少不必要的堆分配。

  • 避免返回局部变量的指针或引用
  • 尽量减少闭包捕获的外部变量
  • 使用值传递而不是指针传递

8 逃逸分析的作用有哪些?

  • 提升内存分配效率:栈上分配比在堆上分配效率更高效,栈上的内存可以自动回收,而堆上的内存需要垃圾回收器管理。
  • 减少垃圾回收开销:减少不必要的堆分配,可以降低垃圾回收的频率和开销。
  • 提高程序性能:优化内存分配,提升程序运行效率。

9 学习交流

为了方便大家一起学习一起进步,我创建了一个学习交流的平台
感兴趣的朋友们可以加我微信:LH913582934,备注:CSDN。

这篇关于Go 语言逃逸分析:内存管理的关键的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/999873

相关文章

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

golang内存对齐的项目实践

《golang内存对齐的项目实践》本文主要介绍了golang内存对齐的项目实践,内存对齐不仅有助于提高内存访问效率,还确保了与硬件接口的兼容性,是Go语言编程中不可忽视的重要优化手段,下面就来介绍一下... 目录一、结构体中的字段顺序与内存对齐二、内存对齐的原理与规则三、调整结构体字段顺序优化内存对齐四、内

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

Go Mongox轻松实现MongoDB的时间字段自动填充

《GoMongox轻松实现MongoDB的时间字段自动填充》这篇文章主要为大家详细介绍了Go语言如何使用mongox库,在插入和更新数据时自动填充时间字段,从而提升开发效率并减少重复代码,需要的可以... 目录前言时间字段填充规则Mongox 的安装使用 Mongox 进行插入操作使用 Mongox 进行更

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep