蛮荆

Go 高性能 Tips

2023-04-10

最简单的方法

升级到稳定的最新版本,享受官方升级带来的红利。

常量与变量

  • 多处使用的变量提取为常量
  • 局部变量使用 := 初始化
  • 局部变量 列表型 数据,可以使用数组替代切片

数据类型

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

扩展阅读

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。