性能优化建议 | 青训营

94 阅读4分钟

性能优化建议

简介

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

Benchmark

简介

  • 基准测试(benchmarking)是一种测量和评估软件性能指标的活动(是一个评价方式)。在某个时候通过基准测试建立一个已知的性能水平(称为基准线)。其在整个计算机领域有着长期的应用,计算机领域应用最成功的就是性能测试。

使用

  • go 语言提供了支持基准性能测试的benchmark工具

image-20230731102339023.png

go test -bench=. -benchmem

结果说明

image-20230731102853014.png

Slice预分配内存

  • 尽可能在使用 make() 初始化切片的时候提供容量信息

    • image-20230731110440742.png
    • 性能方面时间比例接近是1:3
  • 切片本质是一个数组片段的描述

    • 数组指针
    • 片段的长度
    • 片段的容量(不改变内存分配情况下的最大长度)
    • image-20230731110617078.png
  • 切片操作并不会复制切片指向的元素

  • 创建一个新的切片会复用原来切片的底层数组

陷阱:大内存未释放

  • 在以后切片基础上创建切片,不会创建新的底层数组

  • 场景:

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

总结

  • 使用make()函数初始化切片:当你需要创建一个切片并指定其长度和容量时,使用内置的make()函数来分配底层数组的内存。这样可以确保切片有足够的容量来存储元素,避免后续不必要的内存重新分配。
slice := make([]int, 0, 10)  // 创建长度为0,容量为10的切片
  • 避免过多的切片扩容:当切片的长度超过了其容量时,Go语言会自动扩容切片,重新分配更大的底层数组,并将原始数据复制到新数组中。这种扩容操作会导致额外的内存分配和数据复制,影响性能。因此,如果你知道切片的最大长度,最好在创建时就指定足够的容量,避免过多的扩容操作。
  • 使用切片的截取功能:切片提供了截取(slicing)的功能,可以通过指定起始索引和结束索引来获取一个新的切片。这个操作不会分配新的内存,而是共享底层数组。这在某些情况下可以避免创建新的切片,节省内存开销。
  • 及时释放不再使用的切片:如果你有一个很大的切片,但在后续的程序逻辑中不再使用它,建议及时将其置为nil,以便垃圾回收器能够回收底层数组的内存。这样可以避免占用过多的内存空间。

Map预分配内存

image-20230731111020579.png

  • 创建 map 时指定大小,时间上也会有优化

  • 分析:

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

字符串处理

创建的字符串拼接方式:

image-20230731112153336.png

  • 使用 + 拼接字符串的性能最差,strings.Builderbytes.Buffer 接近, strings.Builder更快

  • 分析:

    • 字符串在 go 语言中是不可变类型,占用内存大小是固定的

    • 使用 + 每次都会重新分配内存

    • strings.Builderbytes.Buffer底层都是 []byte 数组

      • bytes.Buffer转化为字符串时重新申请了一块空间
      • strings.Builder直接将底层的 []byte 转换成了字符串类型返回
    • 内存扩容策略,不需要每次拼接重新分配内存

  • 进一步性能提升:

    • 使用字符串预分配方法: Grow
    • image-20230731112955602.png

空结构体

使用空结构体节省内存

  • 空结构体 struct{} 实例不占据任何的内存空间

  • 可作为各种场景下的占位符使用

    • 节省资源
    • 空结构体本身具备很强的语义,即这里这不需要任何值,仅作为占位符
  • 实现 set ,可以考虑用 map 来替代

    • 对于这个场景,只需要用到 map 的键,而不需要值
    • 即使是将 map 的值设置为 bool 类型,也会多占据一个字节空间