Go语言性能优化方法 与 内存管理 | 青训营

113 阅读3分钟

本文章内容将解释在Go语言中管理内存并且优化性能的方法,持续补充鲜活例子。

如何评估性能?

Go语言提供benchmarck工具,即go test -bench=. -benchmem。通过指令可以看到CPU核数、执行次数、每次执行所用时间、单次执行申请内存大小、每次执行申请几次内存。也要小心,越是高级的性能优化方法越容易出现问题。

通过pprof可得知系统在何处耗费多少CPU和memory。Flat==0说明只有其他函数的调用;Flat==cum说明无其他函数的调用。CPU采样对象是函数调用和它们占用的时间。

性能优化方式

建议提前预估好需要的空间。这可以从内存分配的方式解释。Go可以在全局堆空间动态分配内存,也可以在每一个goroutine各自的栈空间。因为栈空间只需要push和pop指令即可,go语言倾向于在栈空间分配内存。 在自动内存管理中,程序语言的运行时系统回收动态内存,避免了手动内存管理,专注于业务逻辑,解决了double-free problemuse-after-free problem。 然而,若变量的生命周期和内存足迹无法在编译时确定,那就需要在堆空间进行动态分配。

  • slice 预分配内存
  • map 预分配内存:不断向map中添加元素的操作会触发map的扩容。
  • 字符串处理使用strings.Builder:ByteBuilder与之速度优势相近,'+'效率最低
  • 使用空结构体节省内存:struct{} 作为占位符 不占据任何内存空间,可节省资源
  • atomic包:通过硬件实现,比通过操作系统实现的锁效率高。

业务服务优化

基础概念包括: 依赖: Service A 的功能实现依赖 Service B 的响应结果,称为 Service A依赖 Service B 调用链路: 能支持一个接口请求的相关服务集合及其相互之间的依赖关系 基础库: 公共的工具包、中间件 优化后需要确认优化前后结果是否发生了变化。所做的实验称为A/B实验,即两个同质的样本组,对其中一个组做出某种改动,来观测该优化对所关注的核心指标是否存在显著影响。

指针相关的讨论

通常,指针指向的数据都在堆上进行分配的。 所以,在程序中减少指针的运用可以减少堆分配。这在实际情况下也很常见。

有一种直观观点认为: 值的拷贝昂贵,最好用指针代替。 但是,在很多情况下,直接的值拷贝比指针要廉价,如:

·       编译器会在解除指针时做检查 目的是在指针为 nil 的情况下直接用 panic() 避免内存泄露。必须在运行时执行更多的代码。如果数据是按值传递的,它不可能是 nil``,就不需要这些了

·       指针局部引用不良 一个函数内部的所有值都会在栈空间上分配。局部引用是编写高效代码的重要环节。它会使得变量数据在CPU 的一级二级缓存中的热度更高,进而减少指令预取时 Cache 不命中的的几率。

·       Cache 层拷贝一堆对象,可粗略地认为和拷贝一个指针效率是一样的。CPU 在各 Cache 层和主内存中以固定大小的 cache 进行内存移动。x86 机器上是 64 字节。