高性能 Rust JSON 库 sonic-rs 开源

技术

picture.image

来源 | CloudWeGo 社区

0 1

sonic-rs 介绍

sonic-rs 是一个基于 SIMD 的高性能 Rust JSON 库,是 sonic JSON 库的 Rust 版本。

字节跳动 sonic 开源项目如今包含了不同语言的多个 JSON 库(如下)。其中,sonic-go 最先开源,使用了 JIT 和 SIMD 技术,sonic-cpp 使用了 C++ 模板和 SIMD 技术,这两个 JSON 库均已经在字节内部得到了较大规模的落地。在成本优化大背景下,为了帮助 Golang 业务迁移 Rust,优化 Rust JSON 性能,我们基于 JSON 方面的优化经验和实践,用纯 Rust 语言开发了高性能的 JSON 库 sonic-rs。

  • sonic(Golang JSON 库):

https://github.com/bytedance/sonic

  • sonic-cpp(C++ JSON 库):

https://github.com/bytedance/sonic-cpp

  • sonic-rs(Rust JSON 库):

https://github.com/cloudwego/sonic-rs

sonic-rs 目前支持的 JSON 功能比较齐全,基本对齐了 serde-json 的相关功能,并且提供更加丰富的功能和更多的高性能接口。sonic-rs 的主要功能特点有:

  • 基本兼容 Serde 生态,同时支持 Volo 中的 FastStr 类型
  • 支持动态类型编解码和按需解析
  • 支持 LazyVaue,RawNumber 等类型
  • 支持 UTF-8 校验和标准浮点数精度

在性能方面,我们基于 serde-rs 官方 benchmark ( https://github.com/serde-rs/json-benchmark) 提供的 Rust 结构体和 JSON 数据,对 serde-json, simd-json 和 sonic-rs 在 Rust 结构体下的解析性能进行了测试,可以发现 sonic-rs 的性能是 simd-json 的 1.5~2 倍,是 serde-json 2 倍:


          
twitter/sonic_rs::from_slice_unchecked
          
                        time:   [694.74 µs 707.83 µs 723.19 µs]
          
twitter/sonic_rs::from_slice
          
                        time:   [796.44 µs 827.74 µs 861.30 µs]
          
twitter/simd_json::from_slice
          
                        time:   [1.0615 ms 1.0872 ms 1.1153 ms]
          
twitter/serde_json::from_slice
          
                        time:   [2.2659 ms 2.2895 ms 2.3167 ms]
          
twitter/serde_json::from_str
          
                        time:   [1.3504 ms 1.3842 ms 1.4246 ms]
          

          
citm_catalog/sonic_rs::from_slice_unchecked
          
                        time:   [1.2271 ms 1.2467 ms 1.2711 ms]
          
citm_catalog/sonic_rs::from_slice
          
                        time:   [1.3344 ms 1.3671 ms 1.4050 ms]
          
citm_catalog/simd_json::from_slice
          
                        time:   [2.0648 ms 2.0970 ms 2.1352 ms]
          
citm_catalog/serde_json::from_slice
          
                        time:   [2.9391 ms 2.9870 ms 3.0481 ms]
          
citm_catalog/serde_json::from_str
          
                        time:   [2.5736 ms 2.6079 ms 2.6518 ms]
          

          
canada/sonic_rs::from_slice_unchecked
          
                        time:   [3.7779 ms 3.8059 ms 3.8368 ms]
          
canada/sonic_rs::from_slice
          
                        time:   [3.9676 ms 4.0212 ms 4.0906 ms]
          
canada/simd_json::from_slice
          
                        time:   [7.9582 ms 8.0932 ms 8.2541 ms]
          
canada/serde_json::from_slice
          
                        time:   [9.2184 ms 9.3560 ms 9.5299 ms]
          
canada/serde_json::from_str
          
                        time:   [9.0383 ms 9.2563 ms 9.5048 ms]
      

02

sonic-rs 优化实践

sonic-rs 的优化主要基于 SIMD,其中部分借鉴了其他 JSON 库,如 simd-json 的优化思路。SIMD (Single instruction, multiple data) 是一种并行优化技术,可以用一条指令,并行处理多个数据。如今大多数 CPU 已经支持了各种 SIMD 指令集。例如,x86_64 架构下的 SSE,AVX2,AVX512, aarch64 架构下的 neon 指令集等。使用 SIMD 指令优化之后,对于合适的任务,程序执行的指令数量会更少,因此性能会更好。

在整体设计上,sonic-rs 并没有采用 simd-json 那种二阶段解析的思路,主要将 SIMD 优化应用于 JSON 解析和序列化中的热点,包括字符串序列化、按需解析和浮点数解析等。

> SIMD 优化字符串序列化

字符串序列化是 JSON 序列化的热点。序列化时,需要扫描字符串中的转义字符。对于较长的字符串,逐个字节判断转义字符的操作是比较耗时的,扫描转义字符非常适合使用 SIMD 来加速。

如果用 AVX2 指令来扫描转义字符,如下面代码所示。这段 SIMD 代码在 haswell 架构下面,开 O3 优化之后,其实只有六条 SIMD 指令,即 6 条 SIMD 指令可以一次性扫描 32个字节。相比较标量代码来说,大大减少了程序指令的数量,从而减少了程序的执行时间。


          
static inline __m256i _mm256_find_quote(__m256i vv) {
          
    __m256i e1 = _mm256_cmpgt_epi8   (vv, _mm256_set1_epi8(-1));
          
    __m256i e2 = _mm256_cmpgt_epi8   (vv, _mm256_set1_epi8(31));
          
    __m256i e3 = _mm256_cmpeq_epi8   (vv, _mm256_set1_epi8('"'));
          
    __m256i e4 = _mm256_cmpeq_epi8   (vv, _mm256_set1_epi8('\\'));
          
    __m256i r1 = _mm256_andnot_si256 (e2, e1);
          
    __m256i r2 = _mm256_or_si256     (e3, e4);
          
    __m256i rv = _mm256_or_si256     (r1, r2);
          
    return rv;
          
}
      

> SIMD 优化按需解析

很多业务场景只用到 JSON 中的部分字段,很适合按需解析,在解析时跳过不需要的 JSON 字段。在跳过 JSON 字段时,难点在于如何高效跳过 JSON 中的 object 和 array。

基于 JSON 中 object 和 array 括号必须匹配的语法规则,sonic-rs 使用 SIMD 实现了高效的括号匹配算法。先通过 SIMD 得到 json object 和 array 的 bitmap,然后通过计算括号的数量来跳过 object 和 array。当发现括号匹配时,就可以跳过该 object 或 array。

picture.image

> SIMD 优化浮点数解析

浮点数解析是 JSON 解析中的一个性能热点。在字节内部,我们发现 JSON 中大部分浮点数的尾数都比较长,也适合使用 SIMD 优化。如下图,对于一段长 16 个字节的浮点数尾数 "1234342112345678":

  • 先将这段字符串读取到向量寄存器里面,此时向量的每个数字还是 ASCII 码的值。
  • 其次,用向量的减法,逐个字节减去 ASCII 码 '0' 得到 v1。这时。v1 里面的数字已经是十进制。
  • 然后,继续对 v1 里面的各个数字用向量指令做两两乘加(高位乘以10 再加上低位),得到 v2。v2 里面的各个数已经是十进制的两位数。
  • 以此类推,利用 SIMD 指令逐层累加,最终就得到 v16。v16 里面是一个 16 位数,即最终的尾数解析结果。
  • 最后,我们再用向量指令把 v16转成 u64 类型。

整个解析过程,不用遍历浮点数尾数的每一个字符,就能完成浮点数尾数解析。

picture.image

03

sonic-rs 现状和规划

sonic-rs 已经开源三个多月,目前迭代到了 0.3 版本,已经支持 Rust stable 版本,并且支持了 aarch64 架构。sonic-rs 沉淀了一些使用文档,用以帮助各方面的开发者:

  • Golang 迁移 Rust 用户使用 sonic-rs:

https://github.com/cloudwego/sonic-rs/blob/main/docs/for\_Golang\_user\_zh.md

  • Rust serde-json 用户迁移 sonic-rs:

https://github.com/cloudwego/sonic-rs/blob/main/docs/serdejson\_compatibility.md

  • 性能优化细节:

https://github.com/cloudwego/sonic-rs/blob/main/docs/performance\_zh.md

后续,sonic-rs 会在性能,易用性和稳定性上面继续打磨,预期会支持对 Bytes/FastStr 等常见数据类型的零拷贝解析,支持运动时检测 SIMD 指令等,欢迎感兴趣的开发者一起加入我们。


项目地址

GitHub: https://github.com/cloudwego

官网: www.cloudwego.io

点击【 阅读原文 】查看
0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论