要准确测量压缩算法真实开销,需在 benchmark 函数开头调用 b.reportallocs(),预热后、循环前调用 b.resettimer(),避免重复初始化压缩器,并确保测试数据规模与实际一致。

go test -bench 怎么写才能测出压缩算法的真实开销
基准测试不加 -benchmem 或忽略 B.ResetTimer(),测出来的只是内存分配抖动,不是压缩耗时。Go 的 runtime.GC() 可能在你测到一半时插进来,结果波动极大。
- 必须在
BenchmarkXXX函数开头调用b.ReportAllocs(),结尾前加b.ResetTimer()(尤其在预热阶段后) - 预热数据要和实际输入规模一致:小文件别用 1KB 样本去压 10MB 流,否则缓存效应会掩盖真实算法差异
- 避免在循环内重复初始化压缩器——比如
zlib.NewWriter每次都 new,会把对象创建成本算进压缩时间里
func BenchmarkGzipCompress(b *testing.B) {
data := make([]byte, 1024*1024) // 1MB 预分配
rand.Read(data)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := gzip.NewWriter(io.Discard)
w.Write(data)
w.Close() // 必须 close,否则压缩流不完整,计时不准确
}
}
gzip/zlib/zstd/lz4 在 Go 里怎么选参数才不算“作弊”
不同库默认压缩级别天差地别:gzip 默认是 gzip.DefaultCompression(-1),而 zstd 的 zstd.EncoderLevel 默认是 zstd.SpeedDefault(相当于中等压缩比)。直接比 raw 耗时,等于拿自行车和电动车比起步速度。
- 横向对比必须固定「压缩比目标」:比如统一用「压缩后体积 ≤ 原始 30%」或统一用
zstd.WithEncoderLevel(zstd.SpeedBetter)对齐 gzip 的gzip.BestSpeed -
zlib和gzip底层都是 deflate,但gzip多了 header+footer 开销,在小包场景下延迟更明显 -
lz4的lz4.CompressBlock是无状态的,适合流式分块;zstd的Encoder支持复用,但需注意Reset()后字典状态是否保留
“内存暴涨”到底是算法问题还是测试写法问题
跑着跑着 go test -bench 报 fatal error: runtime: out of memory,大概率不是算法本身吃内存,而是测试没控制好复用逻辑或缓冲区泄漏。
- 所有
io.Writer实现(如zstd.Encoder)务必显式Close()或Flush(),否则内部 buffer 可能持续增长 - 别在
b.N循环里反复make([]byte, N)—— 改用b.RunParallel+ 预分配 slice 池,否则 GC 压力全算在压缩头上 -
zstd.Encoder如果传了字典(WithEncoderDict),字典数据会在整个 benchmark 过程中驻留内存,得单独测字典加载开销
Mac/ARM64/Linux AMD64 上性能排序为什么总不一致
不是数据不准,是不同 CPU 架构对 SIMD 指令支持差异太大:zstd 在 ARM64 上默认不开 arm64.SCALAR 就可能退化成纯 Go 实现,而 lz4 的 lz4.SSE4 在 M1/M2 上根本不可用。
立即学习“go语言免费学习笔记(深入)”;
- 交叉编译时加
-tags avx2或-tags arm64不等于自动生效,得看具体库是否检查 build tag 并启用对应汇编实现 - Linux 上用
/proc/cpuinfo确认avx2/neon是否开启;Mac 上用sysctl -a | grep machdep.cpu查看指令集支持 - 真要跨平台比较,建议统一关掉硬件加速:
zstd.WithEncoderCRC(false)、lz4.WithBlockChecksum(false),先比纯算法逻辑
压缩算法的“平衡点”不在文档里,而在你业务数据的分布形状里——比如日志文本连续重复多,zstd 的滑动窗口优势才真正体现;而 protobuf 二进制流里长串零值,lz4 的 run-length 优化反而更稳。别信默认参数,先采样你线上 100 个真实 payload 跑一遍 go test -benchmem -count=5。











