simdjson-go:在 Go 中每秒解析千兆字节的 JSON

简介
JSON 已成为网络的“通用语言”。因此,JSON 的解析性能对于许多应用程序来说至关重要。尽管 JSON 的本质简单且便于人类理解,但它并非一种技术上简单的格式,无法以高速进行解析。
最近出现了一些新的设计,其中之一是 Daniel Lemire 和 Geoff Langdale 提出的 simdjson。simdjson
使用了一种新颖的双阶段方法,通过这种方法可以在单个内核上实现每秒解析千兆字节 JSON 的性能。利用所谓的 SIMD 指令,可以每条指令执行更多文本和数字运算,从而极大地提高性能。
在 MinIO,我们一直在开发 simdjson-go,它是对 Golang 的移植。您可能知道 MinIO 是一种基于 S3 API 的高性能对象存储。亚马逊一直在不断地为 S3 API 添加功能,其中最近添加的一项功能被称为 S3 Select。该功能本质上允许应用程序深入了解 JSON 或 CSV 等格式的(大型)Blob 对象的内容。因此,我们可以利用所有可以获得的解析性能,因此对我们来说,进行这项工作很有意义。
在性能方面,simdjson-go
的运行速度平均约为 simdjson 的 40% 到 60%。与 Golang 的标准包 encoding/json
相比,simdjson-go
的速度快约 10 倍。
功能
simdjson-go
是一个验证解析器,这意味着它除了其他操作外,还会验证和检查数值、布尔值等。因此,这些值在解析后可以作为适当的 int
和 float64
表示形式使用。
此外,simdjson-go
具有以下功能
- 无 4 GB 对象限制
- 支持 ndjson(换行符分隔的 JSON)
- 正确的内存管理
- 纯 Go(无需 cgo)
性能对比 simdjson
基于同一组 JSON 测试文件,下图显示了 simdjson
和 simdjson-go
之间的比较(越大越好)。
性能对比 encoding/json
以下是基于同一组 JSON 测试文件,对 Golang 的标准包 encoding/json
进行的性能比较。

设计
simdjson-go
遵循与 simdjson
相同的双阶段设计。在第一阶段,检测结构元素({, }, [, ], :, 和 ,)并将它们作为消息缓冲区中的偏移量转发到第二阶段。第二阶段构建 JSON 文档结构的磁带格式。
请注意,与 simdjson
相反,simdjson-go
向第二阶段输出 uint32
增量(而不是绝对值)。这允许解析任意大的 JSON 文件(只要单个(字符串)元素不超过 4 GB……)。
此外,为了获得更好的性能,两个阶段作为独立的 Go 例程并发运行,并使用 Go 通道在两个阶段之间进行通信。
阶段 1
阶段 1 已使用 c2goasm 从原始 C 代码(包含 SIMD 本征函数)转换为 Golang 汇编代码。它包含五个独立的步骤
find_odd_backslash_sequences
:检测用于转义引号的反斜杠字符find_quote_mask_and_bits
:生成一个掩码,其中引号之间的字符的位被打开find_whitespace_and_structurals
:为空白符生成一个掩码,以及为结构字符生成一个掩码finalize_structurals
:将上面计算的掩码组合成一个最终掩码,其中每个活动位表示输入消息中结构字符的位置。flatten_bits_incremental
:将最终掩码中的活动位输出为增量偏移量。
还有一个最终例程 find_structural_bits_in_slice
,它将所有这些内容组合在一起,并使用消息缓冲区的一部分作为参数调用,以找到增量偏移量。
阶段 2
在阶段 2 中,构建磁带结构。它本质上是一个单一函数,当它找到各种结构字符并构建所遇到的 JSON 文档的层次结构时,它会在函数中跳跃。JSON 元素的值,例如字符串、整数、布尔值等,将被解析并写入磁带。
使用和要求
成功解析 JSON 内容后,simdjson-go
将返回一个迭代器以遍历磁带结构。以下是一个关于如何遍历的示例
for {
typ := iter.Advance()
switch typ {
case simdjson.TypeRoot:
if typ, tmp, err = iter.Root(tmp); err != nil {
return
}
if typ == simdjson.TypeObject {
if obj, err = tmp.Object(obj); err != nil {
return
}
e := obj.FindKey(key, &elem)
if e != nil && elem.Type == simdjson.TypeString {
v, _ := elem.Iter.StringBytes()
fmt.Println(string(v))
}
}
default:
return
}
}
在要求方面,simdjson-go
需要一个支持 AVX2 和 CLMUL 的 CPU。
结论
simdjson-go
是开源的,并根据 Apache 许可证 v2.0 发布。您可以在 Github 上的 github.com/minio/simdjson-go 找到代码。
试一试。我们欢迎任何反馈和/或贡献。