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

推荐订阅源

GbyAI
GbyAI
Blog — PlanetScale
Blog — PlanetScale
The GitHub Blog
The GitHub Blog
Microsoft Security Blog
Microsoft Security Blog
I
InfoQ
A
About on SuperTechFans
T
The Blog of Author Tim Ferriss
D
DataBreaches.Net
L
LangChain Blog
F
Fortinet All Blogs
C
Check Point Blog
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
H
Help Net Security
J
Java Code Geeks
月光博客
月光博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
IT之家
IT之家
aimingoo的专栏
aimingoo的专栏
小众软件
小众软件
宝玉的分享
宝玉的分享
Jina AI
Jina AI

郑文峰的博客

使用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设备
Go语言内存预分配完全指南
zhengwenfeng · 2025-06-14 · via 郑文峰的博客

# 前言

Slice 和 Map 会动态的扩展来适用新的元素数量,空间不足时是会进行分配新的内存、复制、以及旧内存的回收操作,而频繁的调整大小的操作会显著的降低性能。

# Slice 预分配

没有指定长度的创建一个切片, 切片不断地新增元素时,可以看到其容量也是在不断地递增,我们知道切片的底层是数组,是不可变的,所以它会不断地创建的新的数组,然后元素的内容复制到新的数组中,从而导致内存的分配、复制和 GC 的压力。

17584416417421758441641274.png

import "fmt"

func main() {
	var result []int
    s := make([]int, 0)
	for i := 0; i < 1000; i++ {
		s = append(s, i)
		fmt.Printf("Len: %d, Cap: %d\n", len(s), cap(s))
	}
}

1
2
3
4
5
6
7
8
9
10

运行输出:

Len: 1, Cap: 1
Len: 2, Cap: 2
Len: 3, Cap: 4
Len: 4, Cap: 4
Len: 5, Cap: 8

1
2
3
4
5

而我们指定切片的大小,则可以避免以上压力。

result := make([]int, 0, 1000)
for i := 0; i < 10000; i++ {
    result = append(result, i)
}

1
2
3
4

# Benchmark

import (
	"testing"
)

func BenchmarkAppendNoPrealloc(b *testing.B) {
	for range b.N {
		var s []int
		for j := 0; j < 10000; j++ {
			s = append(s, j)
		}
	}
}

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

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

运行结果如下,可以看到吞吐量变大了,速度快了近 4 倍,内存分配的大小和次数变少了。

$ go test -bench=. -benchmem .                              
goos: darwin
goarch: arm64
pkg: main/demo
cpu: Apple M4 Pro
BenchmarkAppendNoPrealloc-12               44973             24788 ns/op          357628 B/op         19 allocs/op
BenchmarkAppendWithPrealloc-12            185312              6448 ns/op           81920 B/op          1 allocs/op
PASS
ok      main/demo       5.005s

1
2
3
4
5
6
7
8
9

# Map 预分配

Map 底层是一个哈希表,也是一个类似于数组的结构,所以我们也可以使用make来对其进行大小的初始化操作。

m := make(map[int]string, 10000)
for i := 0; i < 10000; i++ {
    m[i] = fmt.Sprintf("val-%d", i)
}

1
2
3
4

# 总结

应用场景

  • Slice 和 Map 的数量已知或者可预测时。
  • 程序是一个高吞吐量数据处理的服务。

注意事项

数据数量变化很大不可以预测。过度的分配会导致大量的内存浪费。