前言

最近在项目中用到了sync.Pool来复用我的结构体, 但遇到了点问题。

我的结构体中有几个切片, 并且这几个切片的最终size是比较大的, 但是我的项目中有不稳定因素, 因此这里无法使用固定长度的数组来代替。

可是我使用sync.Pool的目的就是为了复用这些 size, 从而降低GC的压力, 但 对于sync.Pool的使用中, 我必须在 Put 前, 重置某些可能影响复用的字段, 当然, 这些字段中就包含我的这两个切片, 我尝试用 nil切片(nil slice)->即赋值为’nil’ 或是 空切片(empty slice)->即赋值为 ‘[]string{}’ 或 ‘make{[]string,0,0}’ 来对其进行重置, 可由于这种重置过程, 改变了切片底层指针字段指向的地址, 进而造成 原有底层数组 失去引用, 被GC标记给干掉。 于是乎, 我的 我的sync.Pool 实际上只复用了我的结构体对象所占用的size, 并没有复用到 对象中 字段所 指向的 原有堆空间地址(如切片的底层数组), 最终不光没有降低GC的压力, 反而伴随sync.Pool的引入还增加了开销(增加开销的原因是: 其底层会使得GPM机制被短暂的强制绑定, 从而降低了性能)。

方法

经搜索, 找到了解决方法 -> 那就是 使用 [:0] 来清空切片

这种方法得到的空切片, 可以保留切片底层原本的指针字段所指向的地址, 也就是说底层数组并没有被放弃引用, 从而这部分空间就不会被GC给清楚掉。

以下内容来自文章-> (https://islishude.github.io/blog/2020/09/22/golang/

如果要清空一个slice,那么可以简单的赋值为nil,垃圾回收器会自动回收原有的数据。

1
2
3
a := [1,2,3]
a = nil
fmt.Println(a, len(a), cap(a) // [] 0 0

nil slice 和普通 slice一样可以使用 cap len 内置函数,以及被 for range 遍历。本质和 empty slice 性质一样,零长度和零容量,当然也可以使用 append 操作。

但是如果还需要使用 slice 底层内存,那么最佳的方式是 re-slice:

1
2
3
4
a := [1,2,3]
a = a[:0]
fmt.Println(a, len(a), cap(a) // [] 0 3
fmt.Println(a[:1]) // [1]

不过如果序列化成 json 时候,上述二者就不太相同了,nil slice 会编码成 null,而 empty slice 会编码成 []