sonic-rs 0.3.16

Sonic-rs is a fast Rust JSON library based on SIMD
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# sonic-rs

[![Crates.io](https://img.shields.io/crates/v/sonic-rs)](https://crates.io/crates/sonic-rs)
[![Documentation](https://docs.rs/sonic-rs/badge.svg)](https://docs.rs/sonic-rs)
[![Website](https://img.shields.io/website?up_message=cloudwego&url=https%3A%2F%2Fwww.cloudwego.io%2F)](https://www.cloudwego.io/)
[![License](https://img.shields.io/crates/l/sonic-rs)](#license)
[![Build Status][actions-badge]][actions-url]

[actions-badge]: https://github.com/cloudwego/sonic-rs/actions/workflows/ci.yml/badge.svg
[actions-url]: https://github.com/cloudwego/sonic-rs/actions

中文 | [English]README.md

sonic-rs 是一个基于 SIMD 的高性能 JSON 库。它参考了其他开源库如 [sonic_cpp]https://github.com/bytedance/sonic-cpp[serde_json]https://github.com/serde-rs/json[sonic]https://github.com/bytedance/sonic[simdjson]https://github.com/simdjson/simdjson[rust-std]https://github.com/rust-lang/rust/tree/master/library/core/src/num 等。


***对于 Golang 用户迁移 Rust 使用 `sonic_rs`, 请参考 [for_Golang_user_zh.md](https://github.com/cloudwego/sonic-rs/blob/main/docs/for_Golang_user_zh.md)***

***对于 用户从 `serde_json` 迁移 `sonic_rs`, 请参考 [serdejson_compatibility](https://github.com/cloudwego/sonic-rs/blob/main/docs/serdejson_compatibility.md)***

## ***要求/注意事项***

1. 支持 x86_64 或 aarch64。其他架构下走 fallback 逻辑,性能较差。

2. ~~需要 Rust nightly 版本~~ 已经支持 Rust Stable。

3. 在编译选项中开启 `-C target-cpu=native`

## 如何使用 sonic-rs

要确保在 sonic-rs 中使用 SIMD 指令,您需要添加 rustflags `-C target-cpu=native` 并在主机上进行编译。例如,Rust 标志可以在 Cargo [config](.cargo/config) 中配置。

在 Cargo 依赖中添加 sonic-rs:
```
[dependencies]
sonic-rs = "0.3"
```

## 功能

1. JSON 与 Rust 结构体之间的序列化,基于兼容 `serde_json``serde`2. JSON 与 document 之间的序列化,document是可变数据结构
3. 从 JSON 中获取特定字段
4. 将 JSON 解析为惰性迭代器
5. 在默认情况下支持 `LazyValue``Number``RawNumber`(就像 Golang 的 `JsonNumber`)。
6. 浮点数精度默认和 Rust 标准库对齐


## 基准测试

sonic-rs 的主要优化是使用 SIMD。然而,sonic-rs 没有使用来自`simd-json`的两阶段SIMD算法。sonic-rs 主要在以下场景中使用 SIMD:
1. 解析/序列化长 JSON 字符串
2. 解析浮点数的小数部分
3. 从 JSON 中获取特定元素或字段
4. 在解析JSON时跳过空格

有关优化的更多细节,请参见 [performance_zh.md](docs/performance_zh.md)。

基准测试环境:

```
Architecture:        x86_64
Model name:          Intel(R) Xeon(R) Platinum 8260 CPU @ 2.40GHz
```
AArch64 架构下的测试数据见 [benchmark_aarch64.md](docs/benchmark_aarch64.md)。

基准测试主要有两个方面:

- 解析到结构体:定义的结构体和测试数据来自 [json-benchmark]https://github.com/serde-rs/json-benchmark

- 解析到 document

序列化基准测试也是如此。

解析相关 benchmark 都开启了 UTF-8 校验,同时 `serde-json` 开启了 `float_roundtrip` feature, 以便解析浮点数具有足够精度,和 Rust 标准库对齐。

### 解析到结构体

基准测试将把 JSON 解析成 Rust 结构体,JSON 文本中没有未知字段。JSON 中的所有字段都被解析为结构体字段。

Sonic-rs 比 simd-json 更快,因为 simd-json (Rust) 首先将 JSON 解析成 `tape`,然后将 `tape` 解析成 Rust 结构体。Sonic-rs 直接将 JSON 解析成 Rust 结构体,没有临时数据结构。在 citm_catalog 案例中对 [flamegraph](assets/pngs/) 进行了分析。

`cargo bench --bench deserialize_struct -- --quiet`

```
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]
```


### 解析到 document

该测试将把 JSON 解析成 document。由于以下几个原因,Sonic-rs 会看起来更快一些:
- 如上所述,在 sonic-rs 中没有临时数据结构,例如 `tape`- Sonic-rs 使用内存池为整个 document 使用内存区,从而减少内存分配、提高缓存友好性和可变性。
- 如果 JSON 是object, 解析到`sonic_rs::Value`后,底层是一个 Key-Value pair 的数组,而不会建立 HashMap 或 BTreeMap, 因此没有建表开销。

`cargo bench --bench deserialize_value -- --quiet`

```
twitter/sonic_rs_dom::from_slice
                        time:   [621.16 µs 624.89 µs 628.91 µs]
twitter/sonic_rs_dom::from_slice_unchecked
                        time:   [588.34 µs 594.28 µs 601.36 µs]
twitter/simd_json::slice_to_borrowed_value
                        time:   [1.3001 ms 1.3400 ms 1.3853 ms]
twitter/serde_json::from_slice
                        time:   [3.9263 ms 3.9822 ms 4.0463 ms]
twitter/serde_json::from_str
                        time:   [2.8608 ms 2.9187 ms 2.9907 ms]
twitter/simd_json::slice_to_owned_value
                        time:   [1.7870 ms 1.8044 ms 1.8230 ms]

citm_catalog/sonic_rs_dom::from_slice
                        time:   [1.8024 ms 1.8234 ms 1.8469 ms]
citm_catalog/sonic_rs_dom::from_slice_unchecked
                        time:   [1.7280 ms 1.7731 ms 1.8235 ms]
citm_catalog/simd_json::slice_to_borrowed_value
                        time:   [3.5792 ms 3.6082 ms 3.6386 ms]
citm_catalog/serde_json::from_slice
                        time:   [8.4606 ms 8.5654 ms 8.6896 ms]
citm_catalog/serde_json::from_str
                        time:   [9.3020 ms 9.4903 ms 9.6760 ms]
citm_catalog/simd_json::slice_to_owned_value
                        time:   [4.3144 ms 4.4268 ms 4.5604 ms]

canada/sonic_rs_dom::from_slice
                        time:   [5.1103 ms 5.1784 ms 5.2654 ms]
canada/sonic_rs_dom::from_slice_unchecked
                        time:   [4.8870 ms 4.9165 ms 4.9499 ms]
canada/simd_json::slice_to_borrowed_value
                        time:   [12.583 ms 12.866 ms 13.178 ms]
canada/serde_json::from_slice
                        time:   [17.054 ms 17.218 ms 17.414 ms]
canada/serde_json::from_str
                        time:   [17.140 ms 17.363 ms 17.614 ms]
canada/simd_json::slice_to_owned_value
                        time:   [12.351 ms 12.503 ms 12.666 ms]
```



### 序列化 document

`cargo bench --bench serialize_value  -- --quiet`

在以下基准测试中,对于 `twitter` JSON,sonic-rs 看似更快。 因为 `twitter` JSON 包含许多长 JSON 字符串,这非常适合 sonic-rs 的 SIMD 优化。

```
twitter/sonic_rs::to_string
                        time:   [380.90 µs 390.00 µs 400.38 µs]
twitter/serde_json::to_string
                        time:   [788.98 µs 797.34 µs 807.69 µs]
twitter/simd_json::to_string
                        time:   [965.66 µs 981.14 µs 998.08 µs]

citm_catalog/sonic_rs::to_string
                        time:   [805.85 µs 821.99 µs 841.06 µs]
citm_catalog/serde_json::to_string
                        time:   [1.8299 ms 1.8880 ms 1.9498 ms]
citm_catalog/simd_json::to_string
                        time:   [1.7356 ms 1.7636 ms 1.7972 ms]

canada/sonic_rs::to_string
                        time:   [6.5808 ms 6.7082 ms 6.8570 ms]
canada/serde_json::to_string
                        time:   [6.4800 ms 6.5747 ms 6.6893 ms]
canada/simd_json::to_string
                        time:   [7.3751 ms 7.5690 ms 7.7944 ms]
```

### 序列化 Rust 结构体
`cargo bench --bench serialize_struct  -- --quiet`

解释如上所述。

```
twitter/sonic_rs::to_string
                        time:   [434.03 µs 448.25 µs 463.97 µs]
twitter/simd_json::to_string
                        time:   [506.21 µs 515.54 µs 526.35 µs]
twitter/serde_json::to_string
                        time:   [719.70 µs 739.97 µs 762.69 µs]

canada/sonic_rs::to_string
                        time:   [4.6701 ms 4.7481 ms 4.8404 ms]
canada/simd_json::to_string
                        time:   [5.8072 ms 5.8793 ms 5.9625 ms]
canada/serde_json::to_string
                        time:   [4.5708 ms 4.6281 ms 4.6967 ms]

citm_catalog/sonic_rs::to_string
                        time:   [624.86 µs 629.54 µs 634.57 µs]
citm_catalog/simd_json::to_string
                        time:   [624.10 µs 633.55 µs 644.78 µs]
citm_catalog/serde_json::to_string
                        time:   [802.10 µs 814.15 µs 828.10 µs]
```

### 从 JSON 中获取

`cargo bench --bench get_from -- --quiet`

基准测试是从 twitter JSON 中获取特定字段。

- sonic-rs::get_unchecked_from_str: 不校验json
- sonic-rs::get_from_str: 校验json
- gjson::get_from_str: 不校验json

在 get_unchecked_from_str 中,Sonic-rs 利用 SIMD 快速跳过不必要的字段,从而提高性能。

```
twitter/sonic-rs::get_unchecked_from_str
                        time:   [75.671 µs 76.766 µs 77.894 µs]
twitter/sonic-rs::get_from_str
                        time:   [430.45 µs 434.62 µs 439.43 µs]
twitter/gjson::get_from_str
                        time:   [359.61 µs 363.14 µs 367.19 µs]
```

## 用法

### 对 Rust 类型解析/序列化

直接使用 `Deserialize` 或 `Serialize` trait。

```rs
use sonic_rs::{Deserialize, Serialize}; 
// sonic-rs re-exported them from serde
// or use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}

fn main() {
    let data = r#"{
  "name": "Xiaoming",
  "age": 18,
  "phones": [
    "+123456"
  ]
}"#;
    let p: Person = sonic_rs::from_str(data).unwrap();
    assert_eq!(p.age, 18);
    assert_eq!(p.name, "Xiaoming");
    let out = sonic_rs::to_string_pretty(&p).unwrap();
    assert_eq!(out, data);
}
```

### 按路径从 JSON 中获取特定字段

按`pointer` 路径从 JSON 中获取特定字段。返回的是 `LazyValue`,本质上是一段未解析的 JSON 切片。

sonic-rs 提供了 `get` 和 `get_unchecked` 两种接口。请注意,如果使用 `unchecked` 接口,需要保证 输入的JSON 是格式良好且合法的,否则可能返回非预期结果。

```rs
use sonic_rs::JsonValueTrait;
use sonic_rs::{get, get_unchecked, pointer};

fn main() {
    let path = pointer!["a", "b", "c", 1];
    let json = r#"
        {"u": 123, "a": {"b" : {"c": [null, "found"]}}}
    "#;
    let target = unsafe { get_unchecked(json, &path).unwrap() };
    assert_eq!(target.as_raw_str(), r#""found""#);
    assert_eq!(target.as_str().unwrap(), "found");

    let target = get(json, &path);
    assert_eq!(target.as_str().unwrap(), "found");
    assert_eq!(target.unwrap().as_raw_str(), r#""found""#);

    let path = pointer!["a", "b", "c", "d"];
    let json = r#"
        {"u": 123, "a": {"b" : {"c": [null, "found"]}}}
    "#;
    // not found from json
    let target = get(json, &path);
    assert!(target.is_err());
}

```


# 解析/序列化 document

将 JSON 解析为 `sonic_rs::Value`.

```rs
use sonic_rs::{from_str, json};
use sonic_rs::JsonValueMutTrait;
use sonic_rs::{pointer, JsonValueTrait, Value};

fn main() {
    let json = r#"{
        "name": "Xiaoming",
        "obj": {},
        "arr": [],
        "age": 18,
        "address": {
            "city": "Beijing"
        },
        "phones": [
            "+123456"
        ]
    }"#;

    let mut root: Value = from_str(json).unwrap();

    // get key from value
    let age = root.get("age").as_i64();
    assert_eq!(age.unwrap_or_default(), 18);

    // get by index
    let first = root["phones"][0].as_str().unwrap();
    assert_eq!(first, "+123456");

    // get by pointer
    let phones = root.pointer(&pointer!["phones", 0]);
    assert_eq!(phones.as_str().unwrap(), "+123456");

    // convert to mutable object
    let obj = root.as_object_mut().unwrap();
    obj.insert(&"inserted", true);
    assert!(obj.contains_key(&"inserted"));

    let mut object = json!({ "A": 65, "B": 66, "C": 67 });
    *object.get_mut("A").unwrap() = json!({
        "code": 123,
        "success": false,
        "payload": {}
    });

    let mut val = json!(["A", "B", "C"]);
    *val.get_mut(2).unwrap() = json!("D");

    // serialize
    assert_eq!(serde_json::to_string(&val).unwrap(), r#"["A","B","D"]"#);
}
```

### JSON Iterator

将 JSON object 或 array 解析为惰性迭代器。

```rs
use bytes::Bytes;
use faststr::FastStr;
use sonic_rs::JsonValueTrait;
use sonic_rs::{to_array_iter, to_object_iter_unchecked};
fn main() {
    let json = Bytes::from(r#"[1, 2, 3, 4, 5, 6]"#);
    let iter = to_array_iter(&json);
    for (i, v) in iter.enumerate() {
        assert_eq!(i + 1, v.as_u64().unwrap() as usize);
    }

    let json = Bytes::from(r#"[1, 2, 3, 4, 5, 6"#);
    let iter = to_array_iter(&json);
    for elem in iter {
        // do something for each elem

        // deal with errors when invalid json
        if elem.is_err() {
            assert_eq!(
                elem.err().unwrap().to_string(),
                "Expected this character to be either a ',' or a ']' while parsing at line 1 column 17"
            );
        }
    }

    let json = FastStr::from(r#"{"a": null, "b":[1, 2, 3]}"#);
    let iter = unsafe { to_object_iter_unchecked(&json) };
    for ret in iter {
        // deal with errors
        if ret.is_err() {
            println!("{}", ret.unwrap_err());
            return;
        }

        let (k, v) = ret.unwrap();
        if k == "a" {
            assert!(v.is_null());
        } else if k == "b" {
            let iter = to_array_iter(v.as_raw_str());
            for (i, v) in iter.enumerate() {
                assert_eq!(i + 1, v.as_u64().unwrap() as usize);
            }
        }
    }
}
```

### JSON LazyValue & Number & RawNumber

如果我们需要得到原始的 JSON 文本,可以使用 LazyValue. 如果我们需要将 JSON 数字解析为 untyped number,可以使用 Number。 如果我们需要解析 JSON 数字时***不丢失精度**,可以使用 RawNumber,它类似于 Golang 中的 JsonNumber。

详细示例可以在[raw_value.rs](examples/raw_value.rs) 和 [json_number.rs](examples/json_number.rs) 中找到。


### 错误处理

sonic-rs的错误处理参考了 serde-json,同时加上了对错误位置的描述, 例子在[handle_error.rs](examples/handle_error.rs).

## 常见问题

### 关于 UTF-8

sonic-rs 默认开启了 UTF-8 校验,在使用 `unsafe` 的 API时,其内部并未校验 UTF-8。

### 关于浮点数精度

sonic-rs 默认使用和 Rust 标准库一致的浮点数精度,无需像 `serde-json` 那样添加额外的 `float_roundtrip` feature 来保证浮点数精度。

如果想在解析浮点数时,做到精度无损失,例如 Golang `JsonNumber` 和 `serde-json arbitrary_precision`,可以使用 `RawNumber`。

## 致谢

Thanks the following open-source libraries. sonic-rs has some references to other open-source libraries like [sonic_cpp](https://github.com/bytedance/sonic-cpp), [serde_json](https://github.com/serde-rs/json), [sonic](https://github.com/bytedance/sonic), [simdjson](https://github.com/simdjson/simdjson), [yyjson](https://github.com/ibireme/yyjson), [rust-std](https://github.com/rust-lang/rust/tree/master/library/core/src/num) and so on.

我们为了性能重写了来自 sonic-cpp/sonic/simdjson/yyjson 的许多 SIMD 算法。我们重用了来自 serde_json 的反/序列化代码,并修改了必要的部分以与 serde 高度兼容。我们重用了来自 rust-std 的部分浮点解析代码,使其结构更准确。

参考论文:
1. [Parsing Gigabytes of JSON per Second]https://arxiv.org/abs/1902.08318
2. [JSONSki: streaming semi-structured data with bit-parallel fast-forwarding]https://dl.acm.org/doi/10.1145/3503222.3507719


## 如何贡献

请阅读 [CONTRIBUTING.md](CONTRIBUTING.md)。