Go 高性能 Tips
最简单的方法
升级到稳定的最新版本,享受官方升级带来的红利。
常量与变量
- 多处使用的变量提取为常量
- 局部变量使用
:=
初始化 - 局部变量
列表型
数据,可以使用数组替代切片
数据类型
切片
初始化时预分配容量map
初始化时预分配容量map
的键类型尽可能选择整型map
记得及时删除过期数据,定期重置稀疏map
- 优先使用
[]byte
而非string
(标准库和第三方大多数 API 都是[]byte
),避免两者之间类型转换,还可以通过引用机制
进行复用 - 当
列表型
元素是稀疏的(例如有很多0
或者nil
),可以使用map
作为存储结构 - 当
map
只有键
没有值
并且键
的分布连续时,可以使用数组
或切片
实现 - 如果
切片
元素是复杂的数据类型,使用for i, _ := range slice
方式遍历,避免复制元素带来的性能损耗 - 尽量避免
hot path
上面的内存分配、加锁解锁等操作 - 尽量使用具体的数据类型而非
any
- 尽量使用
缓冲通道
而非非缓冲通道
- 小对象使用
值传递
,而非指针传递
- 对于非持久性对象,优先分配到栈
- 大多数情况下,使用指针类型作为方法的
接收者
- 复杂对象参数传递时传递其指针
- 复杂对象存储到切片时存储其指针
- 使用空结构体
struct{}
优化内存 - 注意
内存对齐
标准库
- 整数转字符串使用
strconv.Itoa
- 较长的字符串拼接使用
strings.Builder
- 使用对象池
sync.Pool
复用对象 - 循环中使用
time.After
时记得复用 - 复用
HTTP
链接 - 使用
sync/atomic
包避免锁操作 - 使用 IO 缓冲,如
bufio.NewWrite()
bufio.NewReader()
IO 密集型
场景并发操作- 尽可能避免使用
反射
- 尽可能使用
读写锁
替代互斥锁
- 尽可能减小
锁的粒度
,使用完成后立即释放 hot path
上尽可能避免使用defer
释放锁hot path
上的encoding/binary
相关操作可以手动实现,避免标准库实现中的反射
- 正则表达式如果多次使用,使用
regexp.Compile()
提前编译完成 - 使用
runtime.Callers
打印堆栈信息而非runtime.Stack
- 使用
singleflight
防止缓存击穿 - 合理使用
内联优化
备注
笔者在测试上面的 Tips
有效性时,Go 版本为 go1.19 linux/amd64
, 这些 Tips 可能随着 Go 版本的变化不再适用,读者遇到具体的性能问题时,应该首先分析具体业务场景,以严格的测试结果为准,切莫按图索骥。