pprof 性能分析
- CPU Profiling
- Memory(heap) Profiling
- Block Profiling
- Goroutine Profiling
- Mutex Profiling
基础代码优化
slice
- 使用
make([]T, len, cap) 预分配内存
- 切片本质是对一个数组片段的引用,当使用
append 函数时,如果切片的容量不足,会重新分配一个新的数组,并将原数组的元素复制到新数组中
- 使用
copy 函数复制 slice
copy 函数会在底层使用 memcpy 函数来复制内存,性能更高
map
- 使用
make(map[T]T, cap) 预分配内存
- map 的底层实现是一个哈希表,当使用
make 函数时,可以预分配内存,避免频繁的扩容和内存拷贝
string
- 使用
strings.Builder 构建字符串
string 是不可变类型,每次修改都会创建一个新的字符串对象,性能较低
strings.Builder 底层使用一个可变的 byte 数组
bytes.Buffer 也可以使用,但是需要转换为 string 类型,转换时需要重新申请内存
strings.Builder 底层直接把 byte 数组转换为 string 类型,性能更高
strings.Builder 也可以使用 builder.Grow() 函数预分配内存,避免频繁的扩容和内存拷贝
struct
- struct{} 空结构体不占用任何内存
- 可以用来作为 map 的 value 或者 channel 的值
atomic
- atomic 依赖于 CPU 指令直接在硬件层面实现,性能更高。多数情况下通过自旋完成,Goroutine 不会挂起。
- mutex 依赖调度机制实现,当 Mutex 被占用时,尝试获取锁的 Goroutine 可能被调度器挂起,会伴随上下文切换和用户态/内核态切换的开销
- mutex 用于保护一段代码逻辑,而 atomic 用于保护特定变量
优化注意事项
- 在保证现有功能稳定的前提下改进具体实现
- 测试用例需覆盖尽可能多的场景
- 在文档中记录存在的问题、改进的原因、实现方法和预期效果
- 通过选项控制是否启用新实现
- 保证必要的日志输出,以便于后续排查问题