惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
D
Docker
N
Netflix TechBlog - Medium
罗磊的独立博客
F
Full Disclosure
I
InfoQ
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Register - Security
The Register - Security
The GitHub Blog
The GitHub Blog
U
Unit 42
Microsoft Security Blog
Microsoft Security Blog
Webroot Blog
Webroot Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
博客园 - 【当耐特】
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy International News Feed
Simon Willison's Weblog
Simon Willison's Weblog
T
Threat Research - Cisco Blogs
Y
Y Combinator Blog
P
Proofpoint News Feed
B
Blog RSS Feed
G
GRAHAM CLULEY
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Cisco Talos Blog
Cisco Talos Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Palo Alto Networks Blog
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
P
Privacy & Cybersecurity Law Blog
Know Your Adversary
Know Your Adversary
I
Intezer
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
L
LangChain Blog
B
Blog
雷峰网
雷峰网
K
Kaspersky official blog
S
Secure Thoughts
Security Latest
Security Latest
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Security @ Cisco Blogs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 Go语言Interface Boxing原理与性能优化指南 Go协程池深度解析:原理、实现与最佳实践 使用etcd分布式锁导致的协程泄露与死锁问题 基于pre-commit的Python代码规范落地实践 初识 MCP Server pulsar阻塞导致logstash无法接入日志 django-prometheus使用及源码分析 kube-proxy源码分析 kubernetes service如何通过iptables转发 tcp缓存引起的日志丢失 django-apschedule定时任务异常停止 理解calico容器网络通信方案原理 理解flannel的三种容器网络方案原理 理解Linux IPIP隧道 理解VXLAN网络 理解Linux TunTap设备 快速了解iptables kafka中listener和advertised.listeners的作用 django rest_framework 分页 django后端服务、logstash和flink接入VictoriaMetrics指标监控 python中import原理 docker容器单机网络 手动实现docker容器bridge网络模型 mysql之MVCC原理 mysql之日志 使用java开发logstash的filter插件 使用python实现单例模式的三种方式 redis之缓存 redis之分片集群 redis之哨兵机制 redis之主从库同步 redis之持久化 redis之五种基本数据类型 go中如何处理error pod中将代码与运行环境分离 ddt源码分析 python装饰器的使用方法 读书笔记:如何阅读一本书 使用ddt实现unittest的参数化测试 使用kubeadm安装k8s 优化gin表单的错误提示信息 gin中validator模块的源码分析 go简单使用grpc python简单使用grpc k8s之PV、PVC和StorageClass k8s之StatefulSet k8s之DaemonSet k8s之Job和CronJob k8s之ConfigMap和Secret k8s之Service k8s之Pod k8s之Deployment 容器的本质 docker容器 python迭代器与生成器 python元编程 python垃圾回收机制 python上下文管理器 django rest_framework使用jwt django rest_framework异常处理 django rest_framework 自定义文档 django压缩文件下载 django rest_framework使用pytest单元测试 django restframework choice 自定义输出数据 django Filtering 使用 django viewset 和 Router 配合使用时报的错 django model的序列化 django中使用AbStractUser django.core.exceptions.ImproperlyConfigured Application labels aren't unique, duplicates users django 中 media配置 django 外键引用自身和on_delete参数 django 警告 while time zone support is active Flask使用flask_socketio实现websocket flask结合mongo tornado 文件上传 tornado 使用jwt完成用户异步认证 tornado 用户密码 bcrypt加密 tornado 结合wtforms使用表单操作 tornado finish和write区别 tornado 使用peewee-async 完成异步orm数据库操作 pyspark streaming简介 和 消费 kafka示例 使用hue创建ozzie的pyspark action workflow count的性能优化 django rest_framework Authentication django celery 结合使用 网站
Go语言堆栈分配与逃逸分析深度解析
zhengwenfeng · 2025-06-15 · via 郑文峰的博客

# 1. 前言

# 1.1 为什么需要关注堆栈分配?

在Go语言中,内存分配主要有两种方式:

  • 栈分配:轻量快速,函数结束时自动释放,不产生垃圾
  • 堆分配:需要垃圾回收(GC)参与,开销较大

关键区别

  • 栈分配比堆分配快10-100倍
  • 栈分配不会增加GC压力
  • 堆分配的对象生命周期更长

# 1.2 什么是逃逸分析?

逃逸分析是Go编译器在编译时进行的一种优化技术,它会分析变量的生命周期和使用方式,自动决定将变量分配在栈上还是堆上。

# 2. 逃逸分析实战

# 2.1 如何查看逃逸分析结果

在下面代码中,将x的变量的地址返回出去了,这个时候会将x放到堆上。

func allocate() *int {
	x := 42
	return &x // x escapes to the heap
}

func main() {
	allocate()
}

1
2
3
4
5
6
7
8

通过添加参数-gcflags="-m"可以看到如下逃逸的信息

$ go build -gcflags="-m"  main.go
...
./main.go:4:2: moved to heap: x

1
2
3

# 2.2常见的逃逸场景

  1. 返回局部变量指针
func escape() *int {
    x := 10
    return &x // escapes
}

1
2
3
4

  1. 闭包使用局部变量
func closureEscape() func() int {
    x := 5
    return func() int { return x } // x escapes
}

1
2
3
4

  1. 接口类型转换
func toInterface(i int) interface{} {
    return i // escapes if type info needed at runtime
}

1
2
3

  1. 全局变量赋值
var global *int

func assignGlobal() {
    x := 7
    global = &x // escapes
}

1
2
3
4
5
6

  1. 大尺寸对象
func makeLargeSlice() []int {
    s := make([]int, 10000) // may escape due to size
    return s
}

1
2
3
4

# 3. 堆与栈的性能基准测试

type Data struct {
	A, B, C int
}

// 栈分配
func StackAlloc() Data {
    return Data{1, 2, 3} // stays on stack
}

// 堆分配
func HeapAlloc() *Data {
    return &Data{1, 2, 3} // escapes to heap
}

func BenchmarkStackAlloc(b *testing.B) {
    for b.Loop() {
        _ = StackAlloc()
    }
}

func BenchmarkHeapAlloc(b *testing.B) {
    for b.Loop() {
        _ = HeapAlloc()
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

通过运行会发现两者区别并不大,而且竟然没有堆的申请。这是因为编译器很聪明,它发现通过HeapAlloc返回的指针没有任何意义,所以也就把它放在了栈上。

$ go test -bench=. -benchmem .  
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStackAlloc-12          1000000000               0.2373 ns/op          0 B/op          0 allocs/op
BenchmarkHeapAlloc-12           1000000000               0.2253 ns/op          0 B/op          0 allocs/op
PASS
ok      main/demo       1.176s

1
2
3
4
5
6
7
8
9

我需要强制让它分配到堆上,使用全局赋值,修改代码后如下

type Data struct {
	A, B, C int
}

var sink *Data

func HeapAllocEscape() {
	d := &Data{1, 2, 3}
	sink = d // d escapes to heap
}

func StackAlloc() Data {
	return Data{1, 2, 3} // stays on stack
}

func BenchmarkStackAlloc(b *testing.B) {
	for range b.N {
		_ = StackAlloc()
	}
}

func BenchmarkHeapAlloc(b *testing.B) {
	for range b.N {
		HeapAllocEscape()
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

运行结果如下,使用堆存储的开销:35倍的慢调用,24 字节的分配和 1 次垃圾回收的对象。

$ go test -bench=. -benchmem .  
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkStackAlloc-12          1000000000               0.2285 ns/op          0 B/op          0 allocs/op
BenchmarkHeapAlloc-12           147388731                8.117 ns/op          24 B/op          1 allocs/op
PASS
ok      main/demo       3.064s

1
2
3
4
5
6
7
8
9

# 4. 最佳实践

# 4.1 优化栈分配场景

  • GC 压力大的时候
  • 对于短期的小对象
  • 高频调用的函数内部

# 4.2 不必强求栈分配的场景

  • 工厂方法返回对象指针(Go惯用法)
  • 对象需要跨函数生命周期。
  • 不频繁创建的小对象
  • 优化会影响代码可读性时

# 5. 总结

  1. 逃逸分析是Go的重要优化手段,自动决定变量分配位置。
  2. 栈分配性能优势明显,适合短生命周期对象。
  3. 堆分配虽然较慢,但在某些场景下是必要且合理的。
  4. 优化时要平衡性能与代码质量,避免过度优化。