Golang Go语言中分享一个自己的库,用于尽量不 GC 的内存池

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


Golang Go语言中分享一个自己的库,用于尽量不 GC 的内存池
29 回复

buffer 的内存池。
没点进去之前我以为是变量的内存池,哈哈哈

更多关于Golang Go语言中分享一个自己的库,用于尽量不 GC 的内存池的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


提高性能? 应该数据说一下。

为什么不用标准库里的 sync.Pool … …

看了下,只是 bytes.Buffer 的内存池,用途感觉不是很广; 而且也没有和 sync.Pool 对比的 benchmark

嗯嗯准备做个 benchmark ,已经在业务中体现了,还没做 benchmark

嗯嗯 用途不是很广,主要是防止多协程频繁 GC ,会做个 benchmark ,README 没有贴上我业务内存的对比图

最开始业务里面就是用的 sync.pool 但是在大内存业务下,gc 会撑爆内存,后面使用了这个限制 gc

。。。解释下,为啥用了 sync.pool ,gc 会撑爆内存。。。

本质上是 json 的序列化导致 makeslice 很大,使用 syncpool 并没有限制协程的 buffer 对象,来不及 gc 导致某一时刻峰值很大

我们也遇到 op 一样的问题,json 序列化会撑爆内存,就是因为 sync.Pool + json.Buffer 导致的。
当 99%的数据是小包 1%的数据突然来一个几十 MB 的大包,那么有可能后面 sync.Pool 里面的 buffer 都会变成几十 MB ,就会导致内存爆掉。

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

相关 code 链接 https://go-review.googlesource.com/c/go/+/471200

https://github.com/valyala/bytebufferpool 可以用 fasthttp 作者写的这个,如果少数 buffer 特别大会直接丢掉对应的 buffer 。

我才看了 op 的代码,如果你真的想实现 mem 的高效利用,可以参考上面的链接实现或许会更好!

目前 mempool 有点问题就是,限制了上限,但是释放不了下限,没有考虑到 release 机制, 最终还是可能会把所有 buffer 都撑大!

#11 场景比较特殊,当前绑定了 json 序列化,后期准备改成 pb 一劳永逸

包装下 sync.Pool, 把大包扔掉就好了

#14 唉,encode/json 底层实现用了 全局 sync.Pool ,包括 json-iterator 也是类似实现,所以包不了。 如果 json-iterator 提供设置 自定义的 pool 就好了,可惜也没有

使用 json.Encoder/Decoder 就可以自己管理 buffer pool 了

#16 我确定是我表示不清楚,还是太久了,你忘记了,所以我们说的不是同一个东西,你可以再去看看代码
go<br>// NewEncoder returns a new encoder that writes to w.<br>func NewEncoder(w io.Writer) *Encoder {<br> return &amp;Encoder{w: w, escapeHTML: true}<br>}<br><br>// Encode writes the JSON encoding of v to the stream,<br>// followed by a newline character.<br>//<br>// See the documentation for Marshal for details about the<br>// conversion of Go values to JSON.<br>func (enc *Encoder) Encode(v any) error {<br> if enc.err != nil {<br> return enc.err<br> }<br><br> e := newEncodeState()<br> defer encodeStatePool.Put(e)<br>
go<br>var encodeStatePool sync.Pool<br><br>func newEncodeState() *encodeState {<br> if v := encodeStatePool.Get(); v != nil {<br> e := v.(*encodeState)<br> e.Reset()<br> if len(e.ptrSeen) &gt; 0 {<br> panic("ptrEncoder.encode should have emptied ptrSeen via defers")<br> }<br> e.ptrLevel = 0<br> return e<br> }<br> return &amp;encodeState{ptrSeen: make(map[any]struct{})}<br>}<br>
核心消耗内存的地方是 encodeState

#17 ‘我确定是’ -> ‘我不确定是’

是我想得太简单了😂

不懂为什么会爆,sync pool 既然是循环使用的那 pool 里面的对象数量应该不会很多才对。考虑到 json 的普遍性如果真有问题应该有很多相关 issues 才对,能不能放几个

原来 JSON 序列化会爆内存,怪不得,之前有个奇葩甲方一定要我直接拿 JSON 当数据库,然后占内存严重,我就又偷偷改成普通数据库

#20 其实网上有比较多的案例,只是大部分情况下,遇不到。大多数场景,我们所产生的数据包 浮动是有限的,所以里面的 buffer 大小就算扩容也有限,本身来说 sync.Pool 是会回收,只是比较慢,通常来说一般要经过 2-3 个 GC 标记确认不用了才回收,但是这里一般流量较大,才会使用到 sync.Pool 的场景,所以可能导致一直回收不了。

第二点就是 我们是容器部署,容器层限制了 2G 的内存使用量。 但是这个容器限制对于 Go 语言是感知不到的。在 go >1.19 版本,才出现有一个参数配置好像是 GOMEMLIMIT=xx ,告知 Go 我限制了内存。这个时候感到分配压力,GC 才会频繁活动!

但是我们恰巧是 <1.19 , 我们物理机本身是 32G 内存,所以 Go 感觉内存是杠杠够的,但是 Docker 的 Limit ,导致在 Docker 层直接把进程 Kill 了

你这应该跟池化关系不大,可能就是 JSON 太多太大,又需要常驻内存,导致内存占用高。

是运行好几天之后才爆的,重开程序就低得一批,内存里常驻的数据缓存尺寸固定,怎么可能是太多太大了?

仔细看我的代码实现,会释放

谢谢,其实我在 put 的时候做了一点 trick 的释放和重建,目前够用,后期我会结合你这个代码优化

如果不重用,来不及 gc 就会爆炸的

在Go语言中,创建一个旨在减少垃圾回收(GC)影响的内存池库是一个既实用又富有挑战性的任务。你的努力非常值得赞赏,因为优化内存管理对于提升性能至关重要。

内存池通过预先分配和重用固定大小的内存块,能有效减少堆分配和GC的频率,从而改善应用的响应时间和吞吐量。以下是一些建议,可以帮助你完善这个库:

  1. 线程安全:确保内存池在多线程环境下是安全的。这可能需要使用互斥锁或其他同步机制,但请注意,这可能会引入一定的性能开销。

  2. 对象大小管理:支持多种对象大小的内存池,以满足不同场景的需求。可以通过模板或接口来实现泛型支持,但Go的编译时泛型可能会带来一些限制。

  3. 内存泄露检测:提供工具或机制来检测和防止内存泄露,这是内存池实现中常见的陷阱。

  4. 性能监控:集成性能监控功能,如内存使用率、分配和回收速度等,以便用户能够评估内存池的效果并进行调优。

  5. 文档和示例:编写详细的文档和示例代码,帮助用户快速上手并理解内存池的工作原理和使用方法。

  6. 社区反馈:鼓励社区参与,收集反馈并进行迭代改进。开源项目尤其需要社区的支持来保持活力和持续改进。

期待你的内存池库能够为Go社区带来积极的影响,并祝你在这个项目中取得成功!

回到顶部