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

simdjson-go: Parsing gigabytes of JSON per second in Go

简介

JSON 已成为网络的“通用语言”。因此,JSON 的解析性能对于许多应用程序来说至关重要。尽管 JSON 的本质简单且便于人类理解,但它并非一种技术上简单的格式,无法以高速进行解析。

最近出现了一些新的设计,其中之一是 Daniel Lemire 和 Geoff Langdale 提出的 simdjsonsimdjson 使用了一种新颖的双阶段方法,通过这种方法可以在单个内核上实现每秒解析千兆字节 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 是一个验证解析器,这意味着它除了其他操作外,还会验证和检查数值、布尔值等。因此,这些值在解析后可以作为适当的 intfloat64 表示形式使用。

此外,simdjson-go 具有以下功能

  • 无 4 GB 对象限制
  • 支持 ndjson(换行符分隔的 JSON)
  • 正确的内存管理
  • 纯 Go(无需 cgo)

性能对比 simdjson

基于同一组 JSON 测试文件,下图显示了 simdjsonsimdjson-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 找到代码。

试一试。我们欢迎任何反馈和/或贡献。