分享一个自己 golang 的库,用于尽量不 GC 的内存池

2023-07-12 13:48:30 +08:00
 matytan

实现内存池用于对切片对象进行复用,减少内存分配次数 Mempool 是一个内存池库,用于在处理字节缓冲区时减少分配并提高性能。 https://github.com/matyle/mempool

3052 次点击
所在节点    Go 编程语言
28 条回复
alexsunxl
2023-07-12 14:10:33 +08:00
buffer 的内存池。
没点进去之前我以为是变量的内存池,哈哈哈
vfs
2023-07-12 14:17:29 +08:00
提高性能? 应该数据说一下。
Mohanson
2023-07-12 14:20:50 +08:00
为什么不用标准库里的 sync.Pool ... ...
zdt3476
2023-07-12 14:42:45 +08:00
看了下,只是 bytes.Buffer 的内存池,用途感觉不是很广; 而且也没有和 sync.Pool 对比的 benchmark
matytan
2023-07-12 14:45:40 +08:00
@vfs 嗯嗯准备做个 benchmark ,已经在业务中体现了,还没做 benchmark
matytan
2023-07-12 14:46:54 +08:00
@zdt3476 嗯嗯 用途不是很广,主要是防止多协程频繁 GC ,会做个 benchmark ,README 没有贴上我业务内存的对比图
matytan
2023-07-12 14:49:52 +08:00
@Mohanson 最开始业务里面就是用的 sync.pool 但是在大内存业务下,gc 会撑爆内存,后面使用了这个限制 gc
kkhaike
2023-07-12 15:19:35 +08:00
。。。解释下,为啥用了 sync.pool ,gc 会撑爆内存。。。
matytan
2023-07-12 15:31:52 +08:00
@kkhaike 本质上是 json 的序列化导致 makeslice 很大,使用 syncpool 并没有限制协程的 buffer 对象,来不及 gc 导致某一时刻峰值很大
hopingtop
2023-07-12 17:15:42 +08:00
我们也遇到 op 一样的问题,json 序列化会撑爆内存,就是因为 sync.Pool + json.Buffer 导致的。
当 99%的数据是小包 1%的数据突然来一个几十 MB 的大包,那么有可能后面 sync.Pool 里面的 buffer 都会变成几十 MB ,就会导致内存爆掉。

这个问题,Golang 有最新的提案和实现, 就是动态优化 buffer 的大小。 但是还没有合并!

相关 code 链接 https://go-review.googlesource.com/c/go/+/471200
Trim21
2023-07-12 17:22:22 +08:00
@hopingtop https://github.com/valyala/bytebufferpool 可以用 fasthttp 作者写的这个,如果少数 buffer 特别大会直接丢掉对应的 buffer 。
hopingtop
2023-07-12 17:23:20 +08:00
我才看了 op 的代码,如果你真的想实现 mem 的高效利用,可以参考上面的链接实现或许会更好!

目前 mempool 有点问题就是,限制了上限,但是释放不了下限,没有考虑到 release 机制, 最终还是可能会把所有 buffer 都撑大!
hopingtop
2023-07-12 17:25:40 +08:00
@Trim21 #11 场景比较特殊,当前绑定了 json 序列化,后期准备改成 pb 一劳永逸
Nazz
2023-07-12 17:27:30 +08:00
@hopingtop 包装下 sync.Pool, 把大包扔掉就好了
hopingtop
2023-07-12 17:32:35 +08:00
@Nazz #14 唉,encode/json 底层实现用了 全局 sync.Pool ,包括 json-iterator 也是类似实现,所以包不了。 如果 json-iterator 提供设置 自定义的 pool 就好了,可惜也没有
Nazz
2023-07-12 17:51:49 +08:00
@hopingtop 使用 json.Encoder/Decoder 就可以自己管理 buffer pool 了
hopingtop
2023-07-12 18:42:43 +08:00
@Nazz #16 我确定是我表示不清楚,还是太久了,你忘记了,所以我们说的不是同一个东西,你可以再去看看代码
```go
// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w, escapeHTML: true}
}

// Encode writes the JSON encoding of v to the stream,
// followed by a newline character.
//
// See the documentation for Marshal for details about the
// conversion of Go values to JSON.
func (enc *Encoder) Encode(v any) error {
if enc.err != nil {
return enc.err
}

e := newEncodeState()
defer encodeStatePool.Put(e)
```
```go
var encodeStatePool sync.Pool

func newEncodeState() *encodeState {
if v := encodeStatePool.Get(); v != nil {
e := v.(*encodeState)
e.Reset()
if len(e.ptrSeen) > 0 {
panic("ptrEncoder.encode should have emptied ptrSeen via defers")
}
e.ptrLevel = 0
return e
}
return &encodeState{ptrSeen: make(map[any]struct{})}
}
```
核心消耗内存的地方是 encodeState
hopingtop
2023-07-12 18:43:38 +08:00
@hopingtop #17 '我确定是' -> '我不确定是'
Nazz
2023-07-12 19:48:44 +08:00
@hopingtop 是我想得太简单了😂
matrix1010
2023-07-13 00:33:00 +08:00
不懂为什么会爆,sync pool 既然是循环使用的那 pool 里面的对象数量应该不会很多才对。考虑到 json 的普遍性如果真有问题应该有很多相关 issues 才对,能不能放几个

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/956136

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX