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 版本的变化不再适用,读者遇到具体的性能问题时,应该首先分析具体业务场景,以严格的测试结果为准,切莫按图索骥。