关于后端:高性能-Rust-JSON-库-sonicrs-开源

32次阅读

共计 3831 个字符,预计需要花费 10 分钟才能阅读完成。

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: https://github.com/bytedance/sonic(Golang JSON 库)
  • sonic-cpp: https://github.com/bytedance/sonic-cpp(C++ JSON 库)
  • sonic-rs: https://github.com/cloudwego/sonic-rs(Rust JSON 库)

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]


2. 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 解析和序列化中的热点,包含字符串序列化、按需解析和浮点数解析等。

2.1 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;
}

2.2 SIMD 优化按需解析

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

基于 JSON 中 object 和 array 括号必须匹配的语法规定,sonic-rs 应用 SIMD 实现了高效的括号匹配算法。先通过 SIMD 失去 json object 和 array 的 bitmap,而后通过计算括号的数量来跳过 object 和 array。当发现括号匹配时,就能够跳过该 object 或 array。

2.3 SIMD 优化浮点数解析

浮点数解析是 JSON 解析中的一个性能热点。在字节外部,咱们发现 JSON 中大部分浮点数的尾数都比拟长,也适宜应用 SIMD 优化。如下图,对于一段长 16 个字节的浮点数尾数 ”1234342112345678″:

  1. 先将这段字符串读取到向量寄存器外面,此时向量的每个数字还是 ASCII 码的值。
  2. 其次,用向量的减法,一一字节减去 ASCII 码 ‘0’ 失去 v1。这时。v1 外面的数字曾经是十进制。
  3. 而后,持续对 v1 外面的各个数字用向量指令做两两乘加(高位乘以 10 再加上低位),失去 v2。v2 外面的各个数曾经是十进制的两位数。
  4. 以此类推,利用 SIMD 指令逐层累加,最终就失去 v16。v16 外面是一个 16 位数,即最终的尾数解析后果。
  5. 最初,咱们再用向量指令把 v16 转成 u64 类型。

整个解析过程,不必遍历浮点数尾数的每一个字符,就能实现浮点数尾数解析。

3. 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