wandb 核心依赖探秘:klauspost/compress/zstd 纯 Go 版 Zstandard 压缩技术完全指南
2026/9/24 16:01:37 网站建设 项目流程
  • 机器学习
  • 深度学习
  • 数据可视化
  • 可观测性

【免费下载链接】wandb

The AI developer platform. Use Weights & Biases to train and fine-tune models, and manage models from experimentation to production.

项目地址:https://gitcode.com/gh_mirrors/wa/wandb
点击查看免费下载

Zstandard(简称 zstd)是一种实时压缩算法,在提供高压缩比的同时支持极宽的压缩/速度权衡区间,并拥有非常快的解码器。github.com/klauspost/compress/zstd是 Zstandard 的纯 Go 实现,它在本仓库中以 vendored 依赖的形式存在于 core/vendor/github.com/klauspost/compress/zstd,由 core/go.mod 声明的github.com/klauspost/compress v1.20.0间接引入,并被 Apache Arrow Go 实现的 IPC 层用作 zstd 编解码后端(见 compression.go)。读完本文,你将掌握该库的流式/块式压缩与解压 API、全部关键配置选项、并发与字典用法,以及它在 wandb 核心中的真实落点。

一、包定位:纯 Go、面向 64 位优化的 Zstandard 实现

该包提供 Zstandard 内容的压缩(Compressor)与解压(Decompressor)能力,核心特征包括:

  • 纯 Go 实现:可通过构建标签noasmnounsafe禁用汇编优化与不安全代码特性;
  • 侧重速度:当前实现的高性能压缩算法聚焦于速度,同时兼顾压缩比;
  • 64 位优化:包当前针对 64 位处理器做了大量优化,在 32 位处理器上会明显变慢;
  • 稳定性承诺:压缩器与解压器均标记为 STABLE 状态,项目持续通过模糊测试(fuzz testing)验证,但项目方仍建议针对特定数据类型/尺寸/设置组合自行测试;
  • 许可:以 Go 标准开源许可发布。

在 wandb 仓库中,该包并非被 wandb 自身 Go 源码直接 import,而是作为github.com/apache/arrow-go/v18的传递依赖被 vendored。core/vendor/modules.txt第 693–702 行记录了完整的 vendored 清单,包含github.com/klauspost/compress/zstd及其内部依赖zstd/internal/xxhash。真正使用它的是 Arrow IPC 压缩封装:其中zstdCompressor包装了*zstd.EncoderzstdDecompressor包装了*zstd.Decoder,分别实现了 Arrow IPC 的Compressor/Decompressor接口,从而为 Arrow 数据流提供 zstd 压缩通道。因此,该库的 API 与性能直接关系到 wandb 核心中 Arrow 相关数据路径的编解码体验。

二、安装与引入

该库随github.com/klauspost/compress模块发布,包路径为github.com/klauspost/compress/zstd。对于使用本仓库的开发者,依赖已由 core/go.mod 锁定为v1.20.0并完成 vendoring,无需手动下载;在自己的 Go 工程中引入则执行:

go get -u github.com/klauspost/compress

随后在代码中import "github.com/klauspost/compress/zstd"即可。由于 Go 模块语义,compress/zstd与本仓库核心中的其他 vendored 包遵循相同的版本锁定规则,升级时需同步更新go.modgo.sum(仓库中对应条目见 go.sum)。

三、Compressor:压缩器使用指南

3.1 压缩级别与速度模型

目前实现了一个高速(fastest)和一个中高速(default)压缩器,压缩级别与官方 zstd 参考实现的对应关系如下:

本包级别大致对应官方 zstd 级别
Fastest(最快)level 1
Default(默认)level 3(官方默认)
Better(更好)level 7
Best(最佳)level 11

与 Go 标准库相比:其速度通常是标准库 deflate/gzip 最快模式的2 倍;压缩比约相当于 level 3,但速度通常是其3 倍。性能对比细节见文末“性能基准”一节。

3.2 流式压缩:NewWriter

Encoder 支持两种使用方式:通过io.WriteCloser接口进行流式压缩,或通过EncodeAll进行多个独立任务的块式压缩。较小数据量的编码建议使用EncodeAllNewWriter创建的实例两种方式都可用。

默认选项创建 writer 的经典流式压缩示例:

// Compress input to output. func Compress(in io.Reader, out io.Writer) error { enc, err := zstd.NewWriter(out) if err != nil { return err } _, err = io.Copy(enc, in) if err != nil { enc.Close() return err } return enc.Close() }

写入enc的数据即被编码,Close()调用时输出完整收尾。即使编码失败,也应当调用Close()释放可能持有的资源。

上述写法适合大体积编码;但只要可能就应复用 writer:通过Reset(io.Writer)切换到新的输出,让编码器复用全部内部资源,避免浪费分配。

关于并发,需要留意两点:

  • 默认情况下,流式编码采用“轻量并发”,即最多 2 个 goroutine 参与流的一部分工作。这与WithEncoderConcurrency(n)相互独立(未来可能变化),因此若希望限制未来版本的并发,应显式指定期望的并发数;
  • 若希望流式编码完全不启动异步 goroutine,使用WithEncoderConcurrency(1),此时每个块完成即压缩,写操作会阻塞至该块完成。

3.3 并行流压缩:WithConcurrentBlocks

对于大流量吞吐有极致追求的场景,使用WithConcurrentBlocks(true)搭配WithEncoderConcurrency(n)(n 为想使用的 CPU 核心数)。该模式把输入切分为大段(jobs),由多个 goroutine 同时压缩,机制与 C 版 zstd 的多线程压缩类似:

enc, err := zstd.NewWriter(out, zstd.WithEncoderLevel(zstd.SpeedDefault), zstd.WithEncoderConcurrency(runtime.GOMAXPROCS(0)), zstd.WithConcurrentBlocks(true), )

每个非首个 job 都会从前一个 job 获取重叠前缀作为匹配上下文,因此压缩比只受轻微影响;输出按顺序刷出,最终产生一个合法的单帧 zstd 流。项目文档给出的 1.8GB GOB 流基准(AMD Ryzen 9 9950X)如下:

Level1 线程4 线程16 线程1T 比率16T 比率
fastest783 MB/s2950 MB/s(3.8×)6939 MB/s(8.9×)12.24%12.26%
default728 MB/s2533 MB/s(3.5×)5340 MB/s(7.3×)10.67%10.68%
better434 MB/s1105 MB/s(2.5×)2206 MB/s(5.1×)9.14%9.21%
best129 MB/s367 MB/s(2.8×)884 MB/s(6.8×)8.48%8.63%

(上表为项目 README 提供的基准数据,ratio 列指输出相对输入的比例,越低压缩比越高。)

使用该模式的注意事项:

  • 与字典编码不兼容
  • Flush()会立即派发当前部分完成的 job,对延迟敏感的场景可借此强制产出输出;
  • EncodeAll不受影响——它经由编码器池走自己的并发路径。

压缩级别通过WithEncoderLevel()指定,目前仅支持预定义级别。

3.4 未来兼容性保证

该包处于持续演进中,压缩效率与速度都可能变化:

  • 目标是维持默认效率在官方 zstd(level 3)水平;
  • 不应假设编码输出恒定不变,不要用压缩输出的哈希做相似性比对;
  • 同一代码版本可保证输出一致,未来可能出现破坏这一点的模式,但不会在未显式开启选项的情况下启用;
  • 该编码器不设计为(未来也基本不会)与参考编码器输出完全一致的比特流

另需注意(文档中对比 cgo 解压器时指出):cgo 解压器存在不报告部分输入错误、省略部分错误检查、忽略校验和、以及忽略拼接流(而拼接流是 zstd 规范的一部分)等问题——这也是纯 Go 实现的一个价值点。

3.5 块式压缩:EncodeAll

EncodeAll(src, dst []byte) []bytesrc全部编码并追加到dst可并发调用,每次调用只运行在调用者自己的 goroutine 上。编码后的块可以拼接,拼接结果即为合并输入流的合法输出;EncodeAll的产物既可用流式 Decoder 解码,也可用DecodeAll解码。

import "github.com/klauspost/compress/zstd" // Create a writer that caches compressors. // For this operation type we supply a nil Reader. var encoder, _ = zstd.NewWriter(nil) // Compress a buffer. // If you have a destination buffer, the allocation in the call can also be eliminated. func Compress(src []byte) []byte { return encoder.EncodeAll(src, make([]byte, 0, len(src))) }

块编码尤其要注意复用 encoder:预热期后即可做到零分配;若再提供一个容量足够的 dst 缓冲区,可做到完全零分配。用WithEncoderConcurrency(n)可控制最大并发编码数。同一个 Encoder 同时用于流式与块式编码是安全的。

3.6 编码器选项速查(来自源码)

从 encoder_options.go 可以看到完整的 EOption 集合(函数行号以当前 vendored v1.20.0 为准):

选项源码位置说明
WithEncoderCRC(b bool)encoder_options.go#L78输出附加 CRC 值,输出增大 4 字节;可用ResetWithOptions更改
WithEncoderConcurrency(n int)encoder_options.go#L89设置编码并发数
WithWindowSize(n int)encoder_options.go#L111设置滑动窗口大小,影响内存与压缩比
WithEncoderPadding(n int)encoder_options.go#L142输出填充字节数
WithEncoderLevel(l EncoderLevel)encoder_options.go#L236压缩级别(四个预定义级别之一)
WithZeroFrames(b bool)encoder_options.go#L273空输入时是否输出零帧
WithAllLitEntropyCompression(b bool)encoder_options.go#L285所有字面量熵编码压缩
WithNoEntropyCompression(b bool)encoder_options.go#L297禁用熵编码压缩
WithSingleSegment(b bool)encoder_options.go#L315单段(single segment)模式
WithLowerEncoderMem(b bool)encoder_options.go#L327降低编码器内存占用
WithConcurrentBlocks(b bool)encoder_options.go#L345开启多块并行压缩
WithEncoderDict(dict []byte)encoder_options.go#L382压缩时使用单个字典
WithEncoderDictRaw(id uint32, content []byte)encoder_options.go#L398以原始字典 ID 与内容注册字典
WithEncoderDictDelete()encoder_options.go#L410删除已注册字典

默认值在encoderOptions.setDefault()(encoder_options.go#L36-L48)中定义:并发数为runtime.GOMAXPROCS(0)crc=true、块大小取最大压缩块、窗口大小8 << 20(8 MiB)、级别SpeedDefault。级别到具体编码器的映射见encoderOptions.encoder()(encoder_options.go#L51-L73):Fastest 对应fastEncoder、Default 对应doubleFastEncoder、Better 对应betterFastEncoder、Best 对应bestFastEncoder,且带字典时使用对应的*Dict变体。

四、Decompressor:解压器使用指南

状态:STABLE——仍可能存在细微 bug,但已测试大量内容,并持续接受模糊测试,主要目标是确保任何输入都无法导致解码器崩溃或越过其限制运行

包的设计面向两类主要场景:大数据流与小体积内存缓冲,两者都通过创建Decoder来使用。

4.1 流式解压:NewReader

import "github.com/klauspost/compress/zstd" func Decompress(in io.Reader, out io.Writer) error { d, err := zstd.NewReader(in) if err != nil { return err } defer d.Close() // Copy content... _, err = io.Copy(out, d) return err }

重要:默认设置下不再需要 Reader 时务必调用Close()以停止后台 goroutine。当返回错误(包括流结束的io.EOF)后,goroutine 会自行退出。

流按 4 个异步阶段并发解码以追求最佳吞吐;若希望同步解压,用WithDecoderConcurrency(1),数据只在被请求时才解压。

4.2 缓冲解压:DecodeAll

import "github.com/klauspost/compress/zstd" // Create a reader that caches decompressors. // For this operation type we supply a nil Reader. var decoder, _ = zstd.NewReader(nil, zstd.WithDecoderConcurrency(0)) // Decompress a buffer. We don't supply a destination buffer, // so it will be allocated by the decoder. func Decompress(src []byte) ([]byte, error) { return decoder.DecodeAll(src, nil) }

解码器支持并发解压多个缓冲,默认创建 4 个解压器;可用WithDecoderConcurrency(n)调整允许的并发操作数,WithDecoderConcurrency(0)则创建 GOMAXPROCS 个解压器。

4.3 字典(Dictionaries)

使用字典压缩的数据可以被解压(字典是官方 zstd 针对小数据压缩的利器):

  • 解码端:用WithDecoderDicts(dicts ...[]byte)一次注册一个或多个字典。数据中指定了字典 ID 时会自动选用;复用的 Decoder 保留已注册字典;多个同 ID 字典注册时以最后一个为准
  • 编码端:用WithEncoderDict(dict []byte)启用单个字典,它会无条件使用(即使对压缩无益)。必须使用与压缩时相同的字典才能解压
  • 要获得实际收益,字典应基于相似数据构建(官方zstd --train命令生成);使用不合适的字典可能使输出比不用字典略大
  • 目前用字典压缩存在固定的启动性能开销,实现前应测试性能影响。

4.4 零分配操作与资源管理

解码器设计目标之一是在预热后零分配运行,因此应长期持有(store)解码器

  • 流式解码器复用:Reset(r io.Reader) error切换到另一条流;即使上一条流失败也可安全复用;
  • 释放资源:必须调用Close(),之后该解码器不可再复用,但所有运行中的 goroutine 都会停止;不再需要 Reader 时务必调用;
  • 小缓冲解压可共用单个解码器;解码缓冲时,可传入长度为 0、容量为预期大小的目标切片,避免不必要分配。

4.5 解码器并发模型

  • 缓冲解码器:全部工作在同一 goroutine 上完成,本身不并发;但可同时解码多个缓冲,用WithDecoderConcurrency(n)限制;
  • 流式解码器:创建 goroutine 依次承担 4 个职责——(1)读取输入并切分为块;(2)字面量解压;(3)序列解压;(4)输出流重建。这也意味着解码器会“预读”并预生成数据,保证输出随时可用;
  • 流的并发级别决定了解压会提前多少个块开始工作;由于块之间强依赖前一块的输出,流解码并发有限,实践中通常只能有效利用约3 个核心

4.6 解码器选项速查(来自源码)

完整 DOption 集合见 decoder_options.go:

选项源码位置说明
WithDecoderLowmem(b bool)decoder_options.go#L47降低解码器内存占用
WithDecoderConcurrency(n int)decoder_options.go#L68并发解压数;0 表示 GOMAXPROCS
WithDecoderMaxMemory(n uint64)decoder_options.go#L90解码内存上限
WithDecoderDicts(dicts ...[]byte)decoder_options.go#L112注册字典
WithDecoderDictRaw(id uint32, content []byte)decoder_options.go#L131注册原始字典
WithDecoderMaxWindow(size uint64)decoder_options.go#L150解码窗口上限
WithDecodeAllCapLimit(b bool)decoder_options.go#L168是否限制 DecodeAll 容量
WithDecodeBuffersBelow(size int)decoder_options.go#L181低于该尺寸的缓冲采用特定解码路径
WithDecoderDictDelete(ids ...uint32)decoder_options.go#L203按 ID 删除字典

五、性能基准

以下数据全部来自项目 README 的原始记录,反映该版本库在特定硬件与语料下的表现,不代表当前硬件环境的实测结果。

5.1 流式/缓冲解码基准(AMD Ryzen 9 3950X,AMD64 汇编)

BenchmarkDecoderSilesia-32 5 206878840 ns/op 1024.50 MB/s 49808 B/op 43 allocs/op BenchmarkDecoderEnwik9-32 1 1271809000 ns/op 786.28 MB/s 72048 B/op 52 allocs/op Concurrent blocks, performance(DecodeAll 并行解码): BenchmarkDecoder_DecodeAllParallel/kppkn.gtb.zst-32 67356 17857 ns/op 10321.96 MB/s 22.48 pct 102 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/geo.protodata.zst-32 266656 4421 ns/op 26823.21 MB/s 11.89 pct 19 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/plrabn12.txt.zst-32 20992 56842 ns/op 8477.17 MB/s 39.90 pct 754 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/lcet10.txt.zst-32 27456 43932 ns/op 9714.01 MB/s 33.27 pct 524 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/asyoulik.txt.zst-32 78432 15047 ns/op 8319.15 MB/s 40.34 pct 66 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/alice29.txt.zst-32 65800 18436 ns/op 8249.63 MB/s 37.75 pct 88 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/html_x_4.zst-32 102993 11523 ns/op 35546.09 MB/s 3.637 pct 143 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/paper-100k.pdf.zst-32 1000000 1070 ns/op 95720.98 MB/s 80.53 pct 3 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/fireworks.jpeg.zst-32 749802 1752 ns/op 70272.35 MB/s 100.0 pct 5 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/urls.10K.zst-32 22640 52934 ns/op 13263.37 MB/s 26.25 pct 1014 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/html.zst-32 226412 5232 ns/op 19572.27 MB/s 14.49 pct 20 B/op 0 allocs/op BenchmarkDecoder_DecodeAllParallel/comp-data.bin.zst-32 923041 1276 ns/op 3194.71 MB/s 31.26 pct 0 B/op 0 allocs/op

(“pct”为压缩后大小占原大小的百分比;该项目记录于约 2022 年 5 月,可能已过时。)

5.2 压缩器跨实现对比

README 使用多种语料对比本包(表中zskp)与 Datadog cgo 库(zstd)、标准库 gzip 与本包 gzip 实现(gzstd/gzkp)。level取值中,本包 1=fastest、2=default、3=better、4=best。节选如下:

Silesia 语料(约 202 MB tar):

输出levelinsizeoutsizemillismb/s
zskp121194752073821326634318.47
zskp2211947520676554041508133.96
zskp321194752064746933300067.37
zskp4211947520600735081692611.94
zstd(cgo)121194752073605392543371.56
zstd(cgo)321194752066793289864233.68
zstd(cgo)6211947520629164501913105.66
zstd(cgo)921194752060212393506339.92
gzstd1211947520800077351498134.87
gzkp1211947520800882721009200.31

高度可压缩的 GOB 二进制流(约 1.9 GB):

输出levelinsizeoutsizemillismb/s
zskp119113996162339480963230564.34
zskp219113996162039976944997364.73
zskp3191139961617352652313435135.68
zskp419113996161621952354755938.33
zstd(cgo)119113996162498104242637691.26
zstd(cgo)319113996162081921463490522.31
zstd(cgo)619113996161936320386687272.56
zstd(cgo)9191139961617762038616175112.70
gzstd119113996163573820139046201.49
gzkp119113996163591366694885373.08

enwik9(2006-03-03 英文维基百科前 10^9 字节):

输出levelinsizeoutsizemillismb/s
zskp110000000003438336053687258.64
zskp210000000003170012377672124.29
zskp310000000002919158231592359.89
zskp410000000002617102917769712.27
zstd(cgo)110000000003580720213110306.65
zstd(cgo)310000000003137346724784199.35
zstd(cgo)610000000002951388751029092.68
zstd(cgo)910000000002783487002854933.40
gzstd110000000003825781368608110.78
gzkp110000000003827811605628169.45

高度可压缩 JSON(约 6.27 GB):

输出levelinsizeoutsizemillismb/s
zskp162739517646974395329789611.17
zskp2627395176461087653818553322.49
zskp3627395176451766285844186135.41
zskp4627395176446461711416537336.18
zstd(cgo)162739517647662840378450708.00
zstd(cgo)3627395176466188947610927547.57
zstd(cgo)6627395176464275685922996260.18
zstd(cgo)9627395176460197452352413114.16
gzstd16273951764116439776826793223.32
gzkp16273951764112063185617693338.16

VM 镜像 tar(约 8.56 GB):

输出levelinsizeoutsizemillismb/s
zskp18558382592371840022118206448.29
zskp28558382592332611833737074220.15
zskp3855838259231638423618730693.49
zskp48558382592297048065078386210.41
zstd(cgo)18558382592360925010417136476.27
zstd(cgo)38558382592334167999729262278.92
zstd(cgo)68558382592323584640677904104.77
zstd(cgo)98558382592316077886114094657.91
gzstd18558382592392623499251345158.96
gzkp18558382592396011729836722222.26

CSV 数据(约 3.33 GB):

输出levelinsizeoutsizemillismb/s
zskp133256057526413193329462335.17
zskp2332560575258897612617570180.50
zskp333256057525293292603243297.79
zskp4332560575247494977213802522.98
zstd(cgo)133256057526873996378233385.18
zstd(cgo)3332560575259851441110065315.07
zstd(cgo)6332560575257052295320038158.27
zstd(cgo)933256057525175547976456549.12
gzstd1332560575292865490821270149.11
gzkp1332560575292227321413929227.68

综合可见:本包在 1/2 级(fastest/default)下压缩比与 cgo 实现接近,速度处于同一数量级,且远超 gzip;级别越高,压缩比收益越明显,但耗时增长也越陡峭。选型时应在吞吐与压缩比之间按数据特征取舍。

六、ZIP 归档内的 zstd

可以在 zip 归档内用 zstandard 压缩单个文件(虽然支持面不广,但对内部文件很有用)。为此必须注册压缩器与解压器,代码示例可参考包文档中的ZipCompressor示例(实现位于 zip.go)。

两条关键建议:

  • 强烈建议在单个 zip Reader/Writer 实例上注册,而不是使用全局注册函数——来自不同包的两处全局注册会触发 panic;
  • 最好只维护单一压缩器与解压器实例:它们可被多个 zip 文件并发使用,且单实例有助于复用部分资源。

七、在 wandb 仓库中的实际应用

在 wandb 核心(core 目录)中,klauspost/compress/zstd通过 Apache Arrow Go 库(github.com/apache/arrow-go/v18)被间接引入,具体落点在 Arrow IPC 压缩封装:

  • 第 25 行import "github.com/klauspost/compress/zstd"
  • zstdCompressor(第 48 行起)内嵌*zstd.Encoder,通过zstd.NewWriter(nil)构造,并实现MaxCompressedLenType等接口方法(第 52–78 行);
  • zstdDecompressor(第 89 行起)内嵌*zstd.Decoder,通过zstd.NewReader(nil)构造,实现ResetClose等接口方法(第 93–120 行)。

这意味着凡是启用 zstd 压缩的 Arrow IPC 数据通道,编解码底层都由本包承担;而 wandb 自身 Go 业务代码(core/internal等)并不直接引用它,依赖关系在 core/go.mod 中标记为// indirect。对 wandb 的开发者而言,理解本文所述的并发模型、窗口/内存选项与字典行为,有助于在涉及 Arrow 数据压缩时做出正确的性能与内存取舍。

八、参与贡献

项目欢迎一切贡献:新特性/修复请附带测试,性能增强请附带基准(benchmark)数据;一般反馈与使用经验可提交 issue。该包还内嵌了优秀的 xxhash 实现(版权归 Caleb Spare / cespare,2016),位于 zstd/internal/xxhash。

核心参考路径

  • 包文档:README.md
  • 编码器入口与选项:encoder.go、encoder_options.go
  • 解码器入口与选项:decoder.go、decoder_options.go
  • 字典支持:dict.go
  • ZIP 集成:zip.go
  • 依赖声明:core/go.mod、core/vendor/modules.txt
  • wandb 内实际使用:Arrow IPC 压缩封装
  • 机器学习
  • 深度学习
  • 数据可视化
  • 可观测性

【免费下载链接】wandb

The AI developer platform. Use Weights & Biases to train and fine-tune models, and manage models from experimentation to production.

项目地址:https://gitcode.com/gh_mirrors/wa/wandb
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询