记一次 golang 的 zstd 压缩、解压缩优化
问题背景
1、开发反馈 trs 的 stg 环境开启 zstd 解压缩后,内存有明显持续上涨趋势,最终导致 OOM
如图,内存频繁申请释放,当时分析导致 OOM 的原因是因为 stg 的 CPU 不够,导致 GC 不及时,调整 CPU 资源后确实 OOM 没有了。并未怀疑程序本身的性能问题
2、infra 同学发现 adx 的服务存在 zstd 压缩导致 CPU 资源消耗异常的问题,发现是压缩对象的 init 操作非常重导致。
问题分析
结合上面两次问题,想到 Redis 压缩降本时提交的 go 的 zstd 代码有很大优化空间的。可将 zstd.NewWriter 、zstd.NewReader 等重对象使用 sync.Pool 缓存起来,每次使用时从池中取,用完在放回去,避免频繁 New 对象造成内存申请多从而造成 GC 压力大,CPU 资源消耗高的问题。
预期关键结果(收益)
-
开压缩相关的接口 RT 明显降低,压缩&解压缩申请的内存变少
-
CPU 资源显著降低,部分实例可减少申请 CPU 的 request 和 limit (减少实例数)
总的来说应该可以提高性能,降低资源消耗,可降本增效。
解决
原来的 zstd 压缩代码
// Deprecated
// 该方法已废弃,请使用 CompressWithZstd 代替
func CompressWithZstdOld(data []byte) ([]byte, error) {
if len(data) == 0 {
return data, errors.New("data is empty")
}
var compressedData bytes.Buffer
enc, err := zstd.NewWriter(&compressedData)
if err != nil {
return nil, err
}
_, err = enc.Write(data)
if err != nil {
err := enc.Close()
if err != nil {
return nil, err
}
return nil, err
}
err = enc.Close()
if err != nil {
return nil, err
}
return compressedData.Bytes(), nil
}
优化后的 zstd 压缩代码
var encoderPool = sync.Pool{
New: func() interface{} {
enc, err := zstd.NewWriter(nil)
if err != nil {
log.Fatalf("Failed to create new Zstd Encoder: %v", err)
}
return enc
},
}
// CompressWithZstd zstd 压缩,空字符串返回空字符串
func CompressWithZstd(data []byte) ([]byte, error) {
if len(data) == 0 {
return data, errors.New("data is empty")
}
enc := encoderPool.Get().(*zstd.Encoder)
defer encoderPool.Put(enc)
var compressedData bytes.Buffer
enc.Reset(&compressedData)
_, err := enc.Write(data)
if err != nil {
err := enc.Close()
return nil, err
}
return compressedData.Bytes(), nil
}
原来的 zstd 解压缩代码
// Deprecated
// 该方法已废弃,请使用 DeCompressWithZstd 代替
func DeCompressWithZstdOld(compressedData []byte) ([]byte, error) {
if len(compressedData) == 0 {
return compressedData, errors.New("compressedData is empty")
}
var decompressedData bytes.Buffer
dec, err := zstd.NewReader(bytes.NewReader(compressedData))
if err != nil {
return nil, err
}
_, err = io.Copy(&decompressedData, dec)
if err != nil {
dec.Close()
return nil, err
}
return decompressedData.Bytes(), nil
}
优化后的 zstd 解压缩代码
var decoderPool = sync.Pool{
New: func() interface{} {
dec, err := zstd.NewReader(nil)
if err != nil {
log.Fatalf("Failed to create new Zstd Decoder: %v", err)
}
return dec
},
}
// DeCompressWithZstd zstd 解压,空字符串返回空字符串
func DeCompressWithZstd(compressedData []byte) ([]byte, error) {
if len(compressedData) == 0 {
return compressedData, errors.New("compressedData is empty")
}
dec := decoderPool.Get().(*zstd.Decoder)
defer decoderPool.Put(dec)
var decompressedData bytes.Buffer
dec.Reset(bytes.NewReader(compressedData))
_, err := io.Copy(&decompressedData, dec)
if err != nil {
return nil, err
}
return decompressedData.Bytes(), nil
}
benchmark
测试在本地开发机进行,测试字符串保持一致
goos: darwin
goarch: amd64
pkg: git.gametaptap.com/tapad/go-utils/utils
cpu: Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
BenchmarkCompressWithNewGzip-16 25621 45620 ns/op 4145 B/op 6 allocs/op
BenchmarkCompressWithNewGzipOld-16 6871 186002 ns/op 817830 B/op 23 allocs/op
BenchmarkCompressWithZstd-16 231540 5177 ns/op 3456 B/op 2 allocs/op
BenchmarkCompressWithZstdOld-16 530 1994072 ns/op 23740092 B/op 60 allocs/op
BenchmarkDeCompressWithZstd-16 2183894 538.2 ns/op 1418 B/op 1 allocs/op
BenchmarkDeCompressWithZstdOld-16 76734 15489 ns/op 11662 B/op 36 allocs/op
PASS
性能测试结果解析:从两个维度解析,RT 性能和内存分配都有非常大的提升
-
zstd 压缩:RT 优化前一次压缩需要
1994072ns, 优化后只需要5177ns 。内存更甚,优化前一次压缩需要分配 60次内存,优化后只需要 2次(实际多协程下不只一次) -
zstd 解压缩:RT 优化前一次解压缩需要
15489ns, 优化后只需要538.2ns 。 内存优化前一次解压缩需要分配 36次内存,优化后也只需要分配1次。(实际多协程下不止一次)
从结果看应该是一次性价比很高的优化。
关键结果回收
内存申请数据
- 优化前 zstd 的内存申请量占到了整个应用的 28%,优化后占用 1% 都不到
GC 数据
- 优化前,因为频繁申请内存,GC 压力很大,GC 占用了 CPU 的 61.92%的时间。
- 优化后,GC 占用了 CPU 的时间不到 10%。
接口 RT 数据
优化后 RT 减少 1~3ms ,且 RT 非常稳定,消除了毛刺现象(可能是GC 压力大影响的)
CPU 资源数据
不完全统计,多个服务优化后,总计释放了 542C CPU 的资源。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
GreptimeAI + Xinference 联合方案:高效部署并监控你的 LLM 应用
随着人工智能技术的迅速进步,OpenAI 已经崭露头角,成为该领域的领军者之一。它在多种语言处理任务上表现卓越,包括机器翻译、文本分类和文本生成等方面。随着 OpenAI 的兴起,同时涌现的还有许多其他优质的开源大语言模型,比如 Llama,ChatGLM,Qwen 等等,这些优秀的开源模型也可以帮助团队快速地搭建出一个出色的 LLM 应用。 但面对如此多的选择,如何在减少开发成本的同时,能够统一地使用 OpenAI 的接口?如何能高效地持续监控 LLM 应用的运行表现,而又不增加额外的开发复杂度?在这些问题上,GreptimeAI 和 Xinference 提供了切实可行的解决方案。 什么是 GreptimeAI GreptimeAI 构建在开源时序数据库 GreptimeDB 之上,是为大型语言模型(LLM)应用提供的一套可观测性的解决方案,目前已经支持 LangChain 和 OpenAI 的生态。GreptimeAI 使您能够实时全面地了解成本、性能、流量和安全性方面的情况,帮助团队提升 LLM 应用的可靠性。 什么是 Xinference Xinference 是一个专为大型...
-
下一篇
从 Greenplum 到 Databend,万全网络数据库平台架构演进
作者: 代城 万全网络高级工程师,负责万全网络数据平台整体架构研发工作,拥有超过 7 年的大数据相关技术研发经验,一直关注着开源和云技术的发展。 万全网络科技有限公司是一家专注于 B 端电商物流供应链的公司。致力于为客户提供全面的供应链解决方案,涵盖从产品采购到最终配送的全程服务。 公司的服务包括但不限于:供应链管理,仓储与配送,信息技术支持。 迁移背景 在不断发展的科技环境中,企业往往需要不断调整和优化其技术基础设施以适应变化的业务需求。 万全数据中台架构从 2020 年开始构建,历经 3 年多的时间打磨,已经实现了数仓开发的大部分基础功能,包括数据源管理、数据离线同步、数据实时同步、数据资源目录、数据开发、任务依赖管理以及数据服务接口。 此平台不仅支持数据的采集、清洗、加工,还涵盖了数据对外提供服务的完整链路。数据计算同步和计算引擎都是基于 Spark 构建的,确保了平台的稳定性和高效性。 在过去的几年中,它主要使用 Greenplum 作为大数据应用层数据库,该数据库主要用于支撑万全的业务线报表、大屏展示、帆软报表、SFA 运营报表等应用。然而,随着业务的不断发展和平台的逐步完善...
相关文章
文章评论
共有0条评论来说两句吧...






微信收款码
支付宝收款码