性能调优实践笔记 | 青训营

61 阅读2分钟

Go性能调优实战

1. 性能调优建议

1.1 简介

  • 性能优化的前提是满足正式可靠、简洁清晰等质量因素
  • 性能优化是综合评估,有时候时间效率和空间效率可能对立
  • 避免常见性能陷阱可以保证大部分程序性能
  • 越高级的性能优化手段越容易出现问题

1.2 工具

Go的基准性能测试工具:Benchmark

1.3 Slice、Map预分配内存

  • 切片底层是一个数组片段,有固定的内存分配,Map同理
  • 如果超过固定内存,底层会进行扩容
  • 切片操纵并不复制切片指向的元素
  • 提前分配好空间可以减少拷贝和Rehash的消耗

1.4 字符串处理

  • 字符串拼接时,使用'+'性能最差,strings.Builderbytes.Buffer更好,前者更快
  • 字符串是不可变类型、占用内存固定,拼接时会重新分配内存,而strings.Builderbytes.Buffer底层都是[]byte数组,不需要重新分配内存

1.5 空结构体

  • 空结构体struct{}实例不占据内存空间
  • 可作为占位符使用,更节省资源

1.6 atomic包

  • 对于并发编程来说,atomic性能比加锁高很多,通过操作硬件实现
  • atomic通常用于保护变量,而sync.Mutex通常用于保护一段逻辑
  • 实际用例(int变量加1):atomic.AddInt32(&c.i, 1)

2. 性能分析工具pprof实战

2.1 性能调优原则

  • 依靠数据而不是猜测
  • 定位最大瓶颈而不是细枝末节
  • 不要过早优化
  • 不要过度优化

2.2 pprof功能简介

用于性能数据分析,定位耗费CPU、memory运行指标的文件或函数

  • 分析-Profile
  • 展示-View
  • 工具-Tool
  • 采样-Sample

2.3 pprof排查实战

2.4 pprof的采样过程和原理

  • CPU 采样:采样函数调用和它们占用的时间
  • 堆内存采样:通过内存分配器在堆上分配和释放的内存,记录分配/释放的大小和数量
  • 协程和系统线程采样:记录所有goroutine runtime.main的调用栈信息、程序创建的所有系统线程的信息
  • 阻塞操作和锁竞争采样:采样阻塞操作的次数和耗时、争抢锁的次数和耗时

3. 性能调优案例

3.1 业务服务优化

  • 服务:能单独部署、承载一定功能的程序
  • 依赖:如Service A的功能实现依赖Service B的响应结果,则Service A依赖Service B
  • 调用链路:支持一个接口请求的相关服务集合及其相互间的依赖关系
  • 基础库:公共的工具包、中间件
流程:
graph TD
建立服务性能评估手段 --> 分析性能数据,定位性能瓶颈 --> 重点优化项改造 --> 优化效果验证

3.2 基础库优化

流程:
graph TD
分析基础库核心逻辑和性能瓶颈 --> 内部压测验证 --> 推广业务服务落地验证

3.3 Go语言优化

流程:
graph TD
优化内存分配策略 --> 优化代码编译流程,生成更高效的程序 --> 内部压测验证 --> 推广业务服务落地