性能优化建议丨 青训营笔记

112 阅读4分钟

性能优化建议丨 青训营笔记

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

一、本堂课重点内容

本堂课在满足正确性、可靠性、健壮性、可读性等质量因素的前提下,为提高程序的效率提出了几点性能优化建议,有效提升代码执行效率,同时提升代码的可用性、可维护性和可扩展性。

二、详细知识点介绍

性能评估工具 benchmark

go test -bench=. -benchmem

-bench=. 参数指定要运行的测试的名称,在这种情况下是 ".",表示当前目录中的所有测试。

-benchmem 参数指示 go test 命令显示内存分配的详细信息,这是一个非常有用的工具,可以帮助你评估代码的内存使用情况。

image.png

slice 预分配内存

  • 在尽可能的情况下,在使用 make() 初始化切片时提供容量信息,特别是在追加切片时

image.png

对比两种情况的性能表现,左边是没有预分配内存,右边是预分配内存,结果中可以看出执行时间相差很多,预分配只有一次内存分配。

  • 原理

    • 切片本质是一个数组片段的描述,包括了数组的指针,这个片段的长度和容量(不改变内存分配情况下的最大长度)
    • 切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组,因此切片操作是非常高效的

image.png

type slice struct {
    array unsafe.Pointer
    len int
    cap int
}
  • 切片有三个属性,指针(ptr)、长度(len) 和容量(cap)。append 时有两种场景:

    • 当 append 之后的长度小于等于 cap,将会直接利用原底层数组剩余的空间
    • 当 append 后的长度大于 cap 时,则会分配一块更大的区域来容纳新的底层数组
  • 因此,为了避免内存发生拷贝,如果能够知道最终的切片的大小,预先设置 cap 的值能够获得最好的性能

map 预分配内存

image.png

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

使用 strings.Builder

  • 常见的字符串拼接方式

    • +
    • strings.Builder
    • bytes.Buffer
  • strings.Builder 最快,bytes.Buffer 较快,+ 最慢

  • 原理

    • 字符串在 Go 语言中是不可变类型,占用内存大小是固定的,当使用 + 拼接 2 个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和
    • strings.Builder,bytes.Buffer 的内存是以倍数申请的
    • strings.Builder 和 bytes.Buffer 底层都是 []byte 数组,bytes.Buffer 转化为字符串时重新申请了一块空间,存放生成的字符串变量,而 strings.Builder 直接将底层的 []byte 转换成了字符串类型返回

使用空结构体节省内存

  • 空结构体不占据内存空间,可作为占位符使用

  • 比如实现简单的 Set (一个开源实现)

    • Go 语言标准库没有提供 Set 的实现,通常使用 map 来代替。对于集合场景,只需要用到 map 的键而不需要值

使用 atomic 包

  • 原理

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

三、课后个人总结

  • 为保证大部分程序的性能,需避免常见的性能陷阱

Go语言中常见的性能陷阱:

  1. 内存泄露:造成内存泄露的原因包括使用了不正确的指针,未释放不再使用的内存等。
  2. 频繁的内存分配:在频繁分配内存时会导致内存碎片化,从而影响性能。
  3. 不必要的数据拷贝:在数据处理过程中,如果有大量不必要的数据拷贝,将会降低性能。
  4. 遍历大数据集时的性能问题:在遍历大数据集时,如果使用不当,将会导致性能问题。
  5. 使用了过多的锁:在多线程环境下,如果使用了过多的锁,将会导致性能问题。
  6. 循环嵌套过深:在代码中过深的循环嵌套会导致性能问题。
  7. 使用不当的缓存:在使用缓存时,如果不适当使用,将会导致性能问题。
  8. 使用不当的正则表达式:在使用正则表达式时,如果不适当使用,将会导致性能问题。
  • 针对普通应用代码,不要一味地追求程序的性能,应当在满足正确可靠、简洁清晰等质量要求的前提下提高程序性能

四、引用参考