go后端性能优化 | 青训营笔记

113 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

性能优化

  • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
  • 性能优化是综合评估,有时候时间效率空间效率可能对立

Benchmark

go test -bench=. -benchmem

image.png

  1. BenchmarkFib10是测试函数名,-8是GOMAXPROCS的值为8(cpu核数);
  2. 共执行1855870次,即b.N的值;
  3. 每次执行花费ns;
  4. 每次执行申请多大内存;
  5. 每次执行申请几次内存

Slice预分配内存

与数组不同的是,Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

  • 尽可能在使用make()初始化切片时提供容量信息
    data := make([ ]int, 0, size)

  • 切片本质是一个数组片段描述

    • 包括数组指针
    • 片段的长度
    • 片段的容量(最大长度)
    type slice struct {
        array unsafe.Pointer
        len int
        cap int
    }
    
    • 切片操作并不复制切片指向的元素
    • 创建一个新的切片会复用原来切片的底层数组
  • 另一个陷阱:大内存未释放

    • 在已有切片基础上创建切片,不会创建新的底层数组
    • 场景
      • 原切片较大,代码在原切片的基础上新建小切片
      • 原底层数组在内存中有引用,得不到释放
    • 可使用copy替代re-slice

Map预分配内存

data := make(map[int]int, size)

  • 不断向map中添加元素的操作会触发map的扩容
  • 提前分配好空间可以减少内存拷贝和Rehash的消耗
  • 建议根据实际需求提前估计好需要的空间

字符串处理

  • 使用strings.Builder
    使用 + 拼接性能最差,strings.Builder, bytes.Buffer相近,strings.Buffer更快
  • 分析
    • 字符串在Go语言中是不可变类型,占用内存大小是固定的
    • 使用 + 每次都会重新分配内存
    • strings.Builder, bytes.Buffer底层都是 []byte 数组
    • 内存扩容策略,不需要每次拼接重新分配内存
    • 实际strings.Builderstrings.Buffer快,bytes.Buffer转化为字符串时重新申请了一块内存;
      strings.Builder直接将底层的[]byte转换成了字符串类型返回
    • 为了更进一步提升效率,builder.Grow(n * len(str))预分配内存,bytes.Buffer也有。

空结构体

  • 使用空结构体节省内存
    m := make(map[int]struct{})
    • 空结构体struct{}实例不占任何内存空间
    • 可作为各种场景下的占位符使用
      • 节省资源
      • 空结构体本身具有很强的语义,即这里不需要任何值,仅作为占位符

atomic包

多线程公用计数器,保证计数准确,线程安全
相较于锁机制,性能更高

  • 锁的实现是通过操作系统来实现,属于系统调用
  • atomic操作是通过硬件实现,效率比锁高
  • sync.Mutex应该用来保护一段逻辑,不仅仅用于保护一个变量
  • 对于非数值操作,可以使用atomic.Value,能承载一个interface{}

性能分析工具 pprof

image.png

allocs过去所有内存分配的采样
block导致同步原语阻塞的堆栈跟踪
cmdline当前程序的命令行调用
goroutine所有当前goroutine的堆栈跟踪
heap活动对象的内存分配的采样。在获取堆样本之前,可以指定gc GET参数来运行gc
metux争用互斥锁持有者的堆栈跟踪
profileCPU配置文件。您可以在seconds GET参数中指定持续时间。获取配置文件后,使用go tool pprof命令调查配置文件
threadcreate导致创建新操作系统线程的堆栈跟踪
trace当前程序的执行轨迹。您可以在seconds GET参数中指定持续时间。获取跟踪文件后,使用go tool trace命令调查跟踪
  • cpu
    go tool pprof "http://localhost:6060/debug/pprof/profile?second=10"

    • 命令:topN
      查看占用资源最多的函数 image.png Flat == Cum,函数中没有调用其他函数
      Flat == 0,函数中只有其他函数的调用
    • 命令:list
      根据指定的正则表达式查找代码行
    • 命令:web
      调用关系可视化
  • heap - 堆内存
    go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"

    alloc_objects程序累计申请的对象数
    alloc_space程序累计申请的内存大小
    inuse_objects程序当前持有的对象数
    inuse_space程序当前占用的内存大小
  • goroutine - 协程
    goroutine泄漏也会导致内存泄漏
    go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"

    • 由上到下表示调用顺序
    • 每一块表示一个函数,越长代表占用的cpu的时间越长
    • 火焰图是动态的,支持点块进行分析