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

推荐订阅源

Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
S
Schneier on Security
K
Kaspersky official blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
Project Zero
Project Zero
Recorded Future
Recorded Future
T
The Blog of Author Tim Ferriss
P
Palo Alto Networks Blog
Engineering at Meta
Engineering at Meta
MyScale Blog
MyScale Blog
L
LINUX DO - 热门话题
The Register - Security
The Register - Security
B
Blog
V
Vulnerabilities – Threatpost
M
MIT News - Artificial intelligence
P
Proofpoint News Feed
Cyberwarzone
Cyberwarzone
Latest news
Latest news
Webroot Blog
Webroot Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
美团技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CXSECURITY Database RSS Feed - CXSecurity.com
L
LINUX DO - 最新话题
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
B
Blog RSS Feed
aimingoo的专栏
aimingoo的专栏
The GitHub Blog
The GitHub Blog
D
Docker
G
Google Developers Blog
T
Threatpost
F
Full Disclosure
Attack and Defense Labs
Attack and Defense Labs
L
Lohrmann on Cybersecurity
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
D
DataBreaches.Net
WordPress大学
WordPress大学
F
Fortinet All Blogs
S
Secure Thoughts
Y
Y Combinator Blog
博客园 - Franky
Scott Helme
Scott Helme
Apple Machine Learning Research
Apple Machine Learning Research
Hacker News: Ask HN
Hacker News: Ask HN
酷 壳 – CoolShell
酷 壳 – CoolShell
Cloudbric
Cloudbric
N
News | PayPal Newsroom

郑文峰的博客

使用dify对接飞书多维表格 使用n8n对接飞书多维表格 服务启动时出现 OOM 一次服务升级时pg表DDL执行超时失败 Go语言高效IO缓冲技术详解 Go语言延迟初始化(Lazy Initialization)最佳实践 Go语言字符串拼接性能对比与优化指南 Go语言结构体内存对齐完全指南 Go语言空结构体:零内存消耗的高效编程 Go语言堆栈分配与逃逸分析深度解析 Go语言原子操作完全指南 Go语言内存预分配完全指南 Go语言不可变数据共享:无锁并发编程实践 Go语言零拷贝技术完全指南 Go语言遍历性能深度解析:从原理到优化实践 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语言Interface Boxing原理与性能优化指南
zhengwenfeng · 2025-06-14 · via 郑文峰的博客

# 前言

在Go语言中,interface{} 是一种强大的抽象机制,但将具体类型赋值给 interface{} (称为Boxing)会带来一定的性能开销。本文将深入分析 interface boxing 的原理、性能影响及优化实践。

# Interface Boxing原理

将具体类型的值赋值给interface{}的过程称为Boxing。在这个过程中:

  1. 值会在堆上分配新内存并拷贝
  2. 将指针及对应类型赋值给interface{}变量
  3. 这会带来额外性能开销并增加GC压力

# 基础示例分析

# 值类型赋值

将整型 1 赋值给 interface{} 变量 demo 中

func main() {
	var demo interface{}
	demo = 1   // 发生boxing,整型1逃逸到堆
	fmt.Println(demo)
}

1
2
3
4
5

运行时添加 -gcflags="-m" 参数,查看逃逸分析的结果。可以看到 1 逃逸到堆中了。

$ go run -gcflags="-m" main.go 
# command-line-arguments
./main.go:12:13: inlining call to fmt.Println
./main.go:11:9: 1 escapes to heap
./main.go:12:13: ... argument does not escape
1

1
2
3
4
5
6

# 对比具体类型

我们在对比用具体的类型来赋值 1,可以看到是没有逃逸的,但是发现 demo 发生了逃逸到堆,这是为什么呢?

func main() {
	var demo int
	demo = 1
	fmt.Println(demo)
}

1
2
3
4
5
6

% go run -gcflags="-m" main.go 
# command-line-arguments
./main.go:12:13: inlining call to fmt.Println
./main.go:12:13: ... argument does not escape
./main.go:12:14: demo escapes to heap
1

1
2
3
4
5
6

查看 fmt.Println 的源码实现可以发现,其参数是 interface{} 类型,将 demo 传参给 Println 时,也会发生 Boxing 过程,也会发生在堆中申请新内存以及复制的过程,所以会发生逃逸。

type any = interface{}

func Println(a ...any) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

1
2
3
4
5

# 结构体示例

# 值类型结构体

将一个结构体赋值给 inteface{}

type Person struct {
	Name string
}

func main() {
	var demo interface{}
	demo = Person{}
	fmt.Println(demo)
}

1
2
3
4
5
6
7
8
9

可以看到 Person{} 是有发生逃逸的,这里就是发生了 Boxing

$ go run -gcflags="-m" main.go 
# command-line-arguments
./main.go:12:13: inlining call to fmt.Println
./main.go:11:15: Person{} escapes to heap
./main.go:12:13: ... argument does not escape
{}

1
2
3
4
5
6

# 指针类型结构体

再来看直接将指针赋值给 interface{}

type Person struct {
	Name string
}

func main() {
	var demo interface{}
	demo = &Person{}
	fmt.Println(demo)
}

1
2
3
4
5
6
7
8
9

可以看到这里依然发生了堆逃逸,这是因为 &Person{} 取地址操作本身就是在堆上申请内存的,然后将地址赋值给 interface{} 的变量,这里是没有发生 boxing 的。

go run -gcflags="-m" main.go 
# command-line-arguments
./main.go:12:13: inlining call to fmt.Println
./main.go:11:9: &Person{} escapes to heap
./main.go:12:13: ... argument does not escape
&{}

1
2
3
4
5
6

那么问题来了,这两种方式都会在堆上申请内存,那么两种方式是不是没有区别呢?

# 性能基准测试

# 值类型

func BenchmarkBoxedNotInterface(b *testing.B) {
	jobs := make([]int, 0, 1000)
	for range b.N {
		jobs = jobs[:0]
		for j := 0; j < 1000; j++ {
			jobs = append(jobs, j)
		}
	}
}

func BenchmarkBoxedWithInterface(b *testing.B) {
	jobs := make([]interface{}, 0, 1000)
	for range b.N {
		jobs = jobs[:0]
		for j := 0; j < 1000; j++ {
			jobs = append(jobs, j)
		}
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

BenchmarkBoxedNotInterface 完全不需要申请堆内存,并且比 BenchmarkBoxedWithInterface 要快20多倍。

$ go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: code/interface_demo
cpu: Apple M4 Pro
BenchmarkBoxedNotInterface-12            4832395               249.5 ns/op             0 B/op          0 allocs/op
BenchmarkBoxedWithInterface-12            232382              4596 ns/op            5952 B/op        744 allocs/op
PASS
ok      code/interface_demo     9.662s

1
2
3
4
5
6
7
8
9

# 切片操作性能

我们来对上面两种方式来进行 Benchmark 看看两者性能对比。


type Worker interface {
	Work()
}

type LargeJob struct {
	payload [4096]byte
}

func (LargeJob) Work() {}

func BenchmarkBoxedLargeSlice(b *testing.B) {
	jobs := make([]Worker, 0, 1000)
	for range b.N {
		jobs = jobs[:0]
		for j := 0; j < 1000; j++ {
			var job LargeJob
			jobs = append(jobs, job)
		}
	}
}

func BenchmarkPointerLargeSlice(b *testing.B) {
	jobs := make([]Worker, 0, 1000)
	for range b.N {
		jobs := jobs[:0]
		for j := 0; j < 1000; j++ {
			job := &LargeJob{}
			jobs = append(jobs, job)
		}
	}
}

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
27
28
29
30
31
32

运行结果如下,可以看到两者内存申请是差不多的,但是效率上使用指针的要快上 15%,

$ go test -bench=. -benchmem . 
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkBoxedLargeSlice-12                 2935            406307 ns/op         4096014 B/op       1000 allocs/op
BenchmarkPointerLargeSlice-12               3434            342263 ns/op         4096010 B/op       1000 allocs/op
PASS
ok      main/demo       3.589s

1
2
3
4
5
6
7
8
9

# 函数调用性能


var sink Worker

func call(w Worker) {
	sink = w
}

func BenchmarkCallWithValue(b *testing.B) {
	for range b.N {
		var j LargeJob
		call(j)
	}
}

func BenchmarkCallWithPointer(b *testing.B) {
	for range b.N {
		j := &LargeJob{}
		call(j)
	}
}

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

运行结果如下,可以看到两者内存申请差不多,但指针传递效率要更高。

% go test -bench=. -benchmem . 
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkCallWithValue-12        2959195               388.3 ns/op          4096 B/op          1 allocs/op
BenchmarkCallWithPointer-12      3513249               339.7 ns/op          4096 B/op          1 allocs/op
PASS
ok      main/demo       3.419s

1
2
3
4
5
6
7
8
9

# 什么时候允许Interface Boxing?

  • 接口支持解耦和模块化,所以合理的使用接口来设计 API,即使有 Interface Boxing 的成本也是值得花费的
type Storage interface {
    Save([]byte) error
}
func Process(s Storage) { /* ... */ }

1
2
3
4

  • 如果值很小,成本也是忽略不计的
var i interface{}
i = 123 // safe and cheap

1
2

  • 如果只是短暂的使用,开销也是小的

可以看到fmt.Println 中的实现是用接口作为接收参数

func Println(a ...any) (n int, err error) {
	return Fprintln(os.Stdout, a...)
}

1
2
3

这里即使有Interface Boxing,但只是短暂的一次,成本也低。

fmt.Println("value:", someStruct) // implicit boxing is fine

1

# 最佳实践

  • 传递给接口时使用指针。可以避免内存的重复复制与申请。
  • 如果设计 API 时,类型已经确定并且是稳定的,尽可能避免使用 interface 。
  • 尽可能使用特定类型的容器。