性能优化 | 青训营笔记

50 阅读2分钟

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

一、slice 预分配内存

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

切片本质是一个数组片段的描述,包括了数组的指针,这个片段的长度和容量(不改变内存分配情况下的最大长度)

切片操作并不复制切片指向的元素,创建一个新的切片会复用原来切片的底层数组,因此切片操作是非常高效的

切片有三个属性,指针(ptr)、长度(len) 和容量(cap)。append 时有两种场景:

          当 append 之后的长度小于等于 cap,将会直接利用原底层数组剩余的空间

         当 append 后的长度大于 cap 时,则会分配一块更大的区域来容纳新的底层数组

因此,为了避免内存发生拷贝,如果能够知道最终的切片的大小,预先设置 cap 的值能够获得最好的性能

大内存得不到释放。在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组。因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放。

二、map 预分配内存

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

三、使用 strings.Builder

字符串在 Go 语言中是不可变类型,占用内存大小是固定的,当使用 + 拼接 2 个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和。

          strings.Builder,bytes.Buffer 的内存是以倍数申请的。

          strings.Builder 和 bytes.Buffer 底层都是 []byte 数组,bytes.Buffer 转化为字符串时重新申请了一块空间,存放生成的字符串变量,而 strings.Builder 直接将底层的 []byte 转换成了字符串类型返回。

四、使用空结构体节省内存

空结构体不占据内存空间,可作为占位符使用,比如实现简单的 Set。Go 语言标准库没有提供 Set 的实现,通常使用 map 来代替。对于集合场景,只需要用到 map 的键而不需要值。