一文弄清楚Golang内存逃逸
ztj100 2025-01-11 18:53 27 浏览 0 评论
1. 为什么要了解内存逃逸
c/c++的programmer对于堆内存、栈内存一定非常熟悉,以为内存管理完全由使用者自己管理。Go语言的内存管理完全由Go的runtime接管,那么是不程序员就完全不用care变量是如何分配的呢?
- 减少了gc压力。如果变量都分配到堆上,堆不像栈可以自动清理。它会引起Go频繁地进行垃圾回收,而垃圾回收会占用比较大的系统开销,甚至会导致STW(stop the world)。
- 提高分配效率。堆和栈相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。栈内存分配则会非常快。但当我们的服务发现性能瓶颈,要如何去定位瓶颈,让我们的程序运行的更快,就非常有必要了解Go的内存分配。
2. 什么是内存逃逸
Go语言中局部的非指针变量通常是不受GC管理的,这种Go变量的内存分配称为“栈分配”,处于goroutine自己的栈中。由于Go编译器无法确定其生命周期,因此无法以这种方式分配内存的Go变量会逃逸到堆上,被称为 内存逃逸 。
3. 哪些情况下会发生内存逃逸
先来说一下通过go编译器查看内存逃逸方式go build -gcflags=-m xxx.go
- 局部变量被返回造成逃逸
package main
type User struct {
Namestring
}
func foo(s string) *User {
u := new(User)
u.Name= s
return u // 方法内局部变量返回,逃逸
}
func main() {
user := foo("hui")
user.Name= "dev"
}
//# command-line-arguments
//./escape.go:7:6: can inline foo
//./escape.go:13:6: can inline main
//./escape.go:14:13: inlining call to foo
//./escape.go:7:10: leaking param: s
//./escape.go:8:10: new(User) escapes to heap
//./escape.go:14:13: new(User) does not escape
- interface{}动态类型 逃逸
package main
import "fmt"
func main() {
name := "devhui"
fmt.Println(name)
}
//# command-line-arguments
//./escape_02.go:7:13: inlining call to fmt.Println
//./escape_02.go:7:13: name escapes to heap
//./escape_02.go:7:13: []interface {}{...} does not escape
//<autogenerated>:1: leaking param content: .this
很多函数的参数为interface{} 空接口类型,这些都会造成逃逸。比如
func Printf(format string, a ...interface{}) (n int, err error)
func Sprintf(format string, a ...interface{}) string
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Print(a ...interface{}) (n int, err error)
func Println(a ...interface{}) (n int, err error)
复制代码
编译期间很难确定其参数的具体类型,也能产生逃逸
func main() {
fmt.Println("hello 逃逸")
}
/* 逃逸日志分析
./main.go:5:6: can inline main
./main.go:6:13: inlining call to fmt.Println
./main.go:6:14: "hello 逃逸" escapes to heap
./main.go:6:13: []interface {} literal does not escape
*/
- 栈空间不足逃逸
package main
func main() {
s := make([]int, 1000, 1000)
for index, _ := range s {
s[index] = index
s1 := make([]int, 10000, 10000)
for index, _ := range s1 {
s1[index] = index
}
}
逃逸分析:
./escape_03.go:4:11: make([]int, 1000, 1000) does not escape
./escape_03.go:9:12: make([]int, 10000, 10000) escapes to heap
s足够在栈空间分配没有逃逸;s1空间不够在栈内分配发生了逃逸。
- 变量大小不确定(如 slice 长度或容量不定)
package main
func main() {
s := make([]int, 0, 1000)
for index, _ := range s {
s[index] = index
}
num := 1000
s1 := make([]int, 0, num)
for index, _ := range s1 {
s1[index] = index
}
}
逃逸分析:
./escape_05.go:4:11: make([]int, 0, 1000) does not escape
./escape_05.go:10:12: make([]int, 0, num) escapes to heap
s分配时cap是一个常量没有发生逃逸,s1的cap是一个变量发生了逃逸。
- 闭包
func Increase() func() int {
n := 0
return func() int {
n++
return n
}
}
func main() {
in := Increase()
fmt.Println(in()) // 1
fmt.Println(in()) // 2
}
//./escape_04.go:6:2: moved to heap: n
//./escape_04.go:7:9: func literal escapes to heap
//./escape_04.go:7:9: func literal does not escape
//./escape_04.go:15:16: int(~R0) escapes to heap
//./escape_04.go:15:13: []interface {}{...} does not escape
//./escape_04.go:16:16: int(~R0) escapes to heap
//./escape_04.go:16:13: []interface {}{...} does not escape
//<autogenerated>:1: leaking param content: .this
4. 如何减少逃逸
- 局部切片尽可能确定长度或容量
- benchmark test
import "testing"
// sliceEscape 发生逃逸,在堆上申请切片
func sliceEscape() {
number := 10
s1 := make([]int, 0, number)
for i := 0; i < number; i++ {
s1 = append(s1, i)
}
}
// sliceNoEscape 不逃逸,限制在栈上
func sliceNoEscape() {
s1 := make([]int, 0, 10)
for i := 0; i < 10; i++ {
s1 = append(s1, i)
}
}
func BenchmarkSliceEscape(b *testing.B) {
for i := 0; i < b.N; i++ {
sliceEscape()
}
}
func BenchmarkSliceNoEscape(b *testing.B) {
for i := 0; i < b.N; i++ {
sliceNoEscape()
}
}
- 测试结果:
BenchmarkSliceEscape
BenchmarkSliceEscape-10 53271513 22.09 ns/op
BenchmarkSliceNoEscape
BenchmarkSliceNoEscape-10 187033111 6.458 ns/op
- 合理选择返回值、返回指针
- 返回指针可以避免值的拷贝,但是会导致内存分配逃逸到堆中,增加GC的负担。
- 一般情况下,对于需要修改原对象,或占用内存比较大的对象,返回指针。对于只读或占用内存较小的对象,返回值能够获得更好的性能。
- benchmark test
package escape_bench_02
import "testing"
type St struct {
arr [100]int
}
func retValue() St {
var st St
return st
}
func retPtr() *St {
var st St
return &st
}
func BenchmarkRetValue(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = retValue()
}
}
func BenchmarkRetPtr(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = retPtr()
}
}
- 测试结果
BenchmarkRetValue-10 34714424 34.45 ns/op 0 B/op 0 allocs/op
BenchmarkRetPtr-10 8038676 145.3 ns/op 896 B/op 1 allocs/op
可以看到返回值更快且没有发生堆内存的分配。
- 小的拷贝好过引用
- benchmark test
package escape_bench_03
import "testing"
const capacity = 1024
func arrayFibonacci() [capacity]int {
var d [capacity]int
for i := 0; i < len(d); i++ {
if i <= 1 {
d[i] = 1
continue
}
d[i] = d[i-1] + d[i-2]
}
return d
}
func sliceFibonacci() []int {
d := make([]int, capacity)
for i := 0; i < len(d); i++ {
if i <= 1 {
d[i] = 1
continue
}
d[i] = d[i-1] + d[i-2]
}
return d
}
func BenchmarkArray(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = arrayFibonacci()
}
}
func BenchmarkSlice(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = sliceFibonacci()
}
}
- 测试结果:
BenchmarkArray-10 346110 2986 ns/op 0 B/op 0 allocs/op
BenchmarkSlice-10 389745 2849 ns/op 8192 B/op 1 allocs/op
那么多大的变量才算是小变量呢? 对 Go 编译器而言,超过一定大小的局部变量将逃逸到堆上,不同 Go 版本的大小限制可能不一样。一般是 < 64KB,局部变量将不会逃逸到堆上。
- 返回值使用确定的类型
- benchmark test
package escape_bench_04
import "testing"
const capacity = 1024
func returnArray() [capacity]int {
var arr [capacity]int
for i := 0; i < len(arr); i++ {
arr[i] = 1000
}
return arr
}
func returnInterface() interface{} {
var arr [capacity]int
for i := 0; i < len(arr); i++ {
arr[i] = 1000
}
return arr
}
func BenchmarkReturnArray(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = returnArray()
}
}
func BenchmarkReturnInterface(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = returnInterface()
}
}
- 测试结果
相关推荐
- sharding-jdbc实现`分库分表`与`读写分离`
-
一、前言本文将基于以下环境整合...
- 三分钟了解mysql中主键、外键、非空、唯一、默认约束是什么
-
在数据库中,数据表是数据库中最重要、最基本的操作对象,是数据存储的基本单位。数据表被定义为列的集合,数据在表中是按照行和列的格式来存储的。每一行代表一条唯一的记录,每一列代表记录中的一个域。...
- MySQL8行级锁_mysql如何加行级锁
-
MySQL8行级锁版本:8.0.34基本概念...
- mysql使用小技巧_mysql使用入门
-
1、MySQL中有许多很实用的函数,好好利用它们可以省去很多时间:group_concat()将取到的值用逗号连接,可以这么用:selectgroup_concat(distinctid)fr...
- MySQL/MariaDB中如何支持全部的Unicode?
-
永远不要在MySQL中使用utf8,并且始终使用utf8mb4。utf8mb4介绍MySQL/MariaDB中,utf8字符集并不是对Unicode的真正实现,即不是真正的UTF-8编码,因...
- 聊聊 MySQL Server 可执行注释,你懂了吗?
-
前言MySQLServer当前支持如下3种注释风格:...
- MySQL系列-源码编译安装(v5.7.34)
-
一、系统环境要求...
- MySQL的锁就锁住我啦!与腾讯大佬的技术交谈,是我小看它了
-
对酒当歌,人生几何!朝朝暮暮,唯有己脱。苦苦寻觅找工作之间,殊不知今日之事乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一...
- MySQL字符问题_mysql中字符串的位置
-
中文写入乱码问题:我输入的中文编码是urf8的,建的库是urf8的,但是插入mysql总是乱码,一堆"???????????????????????"我用的是ibatis,终于找到原因了,我是这么解决...
- 深圳尚学堂:mysql基本sql语句大全(三)
-
数据开发-经典1.按姓氏笔画排序:Select*FromTableNameOrderByCustomerNameCollateChinese_PRC_Stroke_ci_as//从少...
- MySQL进行行级锁的?一会next-key锁,一会间隙锁,一会记录锁?
-
大家好,是不是很多人都对MySQL加行级锁的规则搞的迷迷糊糊,一会是next-key锁,一会是间隙锁,一会又是记录锁。坦白说,确实还挺复杂的,但是好在我找点了点规律,也知道如何如何用命令分析加...
- 一文讲清怎么利用Python Django实现Excel数据表的导入导出功能
-
摘要:Python作为一门简单易学且功能强大的编程语言,广受程序员、数据分析师和AI工程师的青睐。本文系统讲解了如何使用Python的Django框架结合openpyxl库实现Excel...
- 用DataX实现两个MySQL实例间的数据同步
-
DataXDataX使用Java实现。如果可以实现数据库实例之间准实时的...
- MySQL数据库知识_mysql数据库基础知识
-
MySQL是一种关系型数据库管理系统;那废话不多说,直接上自己以前学习整理文档:查看数据库命令:(1).查看存储过程状态:showprocedurestatus;(2).显示系统变量:show...
- 如何为MySQL中的JSON字段设置索引
-
背景MySQL在2015年中发布的5.7.8版本中首次引入了JSON数据类型。自此,它成了一种逃离严格列定义的方式,可以存储各种形状和大小的JSON文档,例如审计日志、配置信息、第三方数据包、用户自定...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)