V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
simman
V2EX  ›  程序员

如何解决 Dart 解析大 Json 文件慢

  •  
  •   simman · 2023-07-05 10:12:38 +08:00 · 3605 次点击
    这是一个创建于 505 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在写 Flutter 应用,发现 Dart 解析比较大的 json 比较慢,会影响 UI 线程,使用 compute 的话,的确不影响 UI 线程了,但是解析起来更慢,想着可以使用 flutter rust bridge 做个 json 解析库,不过我用 rust 写个测试程序,发现解析 json 比 dart 快不了多少,各位有啥好意见没?

    各语言测试:

    time node index.js                           1.37s user 0.19s system 121% cpu 1.282 total
    time go build && ./main                      1.59s user 0.02s system 80% cpu 2.002 total
    time python3.9 main.py                       2.45s user 0.27s system 98% cpu 2.762 total
    time cargo run --release                     2.46s user 0.42s system 97% cpu 2.972 total
    time dart compile exe main.dart && bin/main  4.62s user 0.45s system 114% cpu 4.415 total
    

    rust code:

    use std::fs::File;
    use std::io::{Read};
    
    use serde_json::Value;
    
    fn parse_json(contents: Vec<u8>) {
        let now = std::time::Instant::now();
        let _: Value = serde_json::from_slice(&contents).unwrap();
        let elapsed = now.elapsed();
        println!("elapsed: {:?}", elapsed);
    }
    
    fn main() {
        let contents = {
            let mut vec = Vec::new();
            // https://github.com/json-iterator/test-data/blob/master/large-file.json
            File::open("large-file.json").unwrap().read_to_end(&mut vec).unwrap();
            vec
        };
        for _ in 0..10 {
            parse_json(contents.clone());
        }
    }
    

    测试机器:MacBook Pro (16-inch, 2019) 2.3 GHz 八核 Intel Core i9 32 GB 2667 MHz DDR4

    第 1 条附言  ·  2023-07-05 10:47:21 +08:00

    不好意思,上面使用time统计时间是不包含程序编译时间的,只是发主题的时候合到一起的。

    for 循环打印的结果如下:

    NodeJs: 110 ms
    NodeJs: 107 ms
    NodeJs: 103 ms
    NodeJs: 106 ms
    NodeJs: 128 ms
    NodeJs: 94 ms
    NodeJs: 93 ms
    NodeJs: 92 ms
    NodeJs: 98 ms
    NodeJs: 93 ms
    
    Dart: 384 ms
    Dart: 390 ms
    Dart: 377 ms
    Dart: 395 ms
    Dart: 356 ms
    Dart: 381 ms
    Dart: 378 ms
    Dart: 370 ms
    Dart: 388 ms
    耗时: 379 ms
    
    Python: 241.057 ms
    Python: 233.375 ms
    Python: 249.352 ms
    Python: 244.968 ms
    Python: 249.487 ms
    Python: 288.286 ms
    Python: 263.347 ms
    Python: 255.939 ms
    Python: 256.224 ms
    Python: 252.851 ms
    
    Golang: 171 ms
    Golang: 162 ms
    Golang: 160 ms
    Golang: 161 ms
    Golang: 161 ms
    Golang: 160 ms
    Golang: 165 ms
    Golang: 157 ms
    Golang: 161 ms
    Golang: 158 ms
    
    Rust: 297.331601ms
    Rust: 268.03852ms
    Rust: 280.606788ms
    Rust: 272.826854ms
    Rust: 293.53427ms
    Rust: 269.841906ms
    Rust: 285.450976ms
    Rust: 288.862249ms
    Rust: 275.984671ms
    Rust: 276.715469ms
    
    38 条回复    2023-07-06 09:51:28 +08:00
    MoYi123
        1
    MoYi123  
       2023-07-05 10:28:46 +08:00
    为什么要把编译的时间也算进去?
    codehz
        2
    codehz  
       2023-07-05 10:32:43 +08:00 via iPhone   ❤️ 1
    你可以考虑一下解析 json 的目的,如果可行的话,不需要先变成一个对象,而是直接从流式解析中提取需要的数据
    simman
        3
    simman  
    OP
       2023-07-05 10:36:44 +08:00
    @MoYi123 go 、rust 、dart 实际是编译完后再 time 执行的。上面写的有问题。
    newmlp
        4
    newmlp  
       2023-07-05 10:40:36 +08:00
    你这都把程序启动时间算上了,肯定不准啊,json 解析才能用多少时间估计 1ms 都不到
    tool2d
        5
    tool2d  
       2023-07-05 11:02:21 +08:00
    纯好奇,用自己写的库解析了一下主贴里的 24M json ,竟然要 1 秒。

    发现 dart 这些库,已经很强了。
    wxf666
        6
    wxf666  
       2023-07-05 11:03:48 +08:00
    你要存啥呢?用个普通数据库也不错呀。。

    比如,SQLite 解析 JSON 也挺快:

    ```
    sqlite> .timer on
    sqlite> SELECT json_array_length(readFile('large-file.json'));
    11351
    Run Time: real 0.089 user 0.046875 sys 0.046875
    ```
    visper
        7
    visper  
       2023-07-05 11:05:57 +08:00
    rust 比 python 还慢.谁说 rust 快的
    lisongeee
        8
    lisongeee  
       2023-07-05 11:07:15 +08:00
    json 解析函数可以改成异步嘛?拆成多个子任务

    let btachTaskNum = 0;
    while(isParsing){
    dobtachTask();
    btachTaskNum++;
    if(btachTaskNum%1000==0){
    await nextTick()
    }
    }
    tool2d
        9
    tool2d  
       2023-07-05 11:07:36 +08:00
    二楼说的也对,看具体使用目的,如果预处理一次,全部变成二进制数转节点,变成特定格式,载入和查询起来都是很快的。

    你要暴力遍历巨量文本的 json ,速度上不去。要动态查询某些节点,速度还是可以提升一下的。
    lisongeee
        10
    lisongeee  
       2023-07-05 11:09:19 +08:00
    艹,我刚刚我回答不符合题意,当作没看见吧
    tool2d
        11
    tool2d  
       2023-07-05 11:17:24 +08:00
    @lisongeee 其实可以优化成异步或者多线程版本,那个 large-file.json 结构和 csv 一样,一行行很有规律。

    但是不是所有的 json 都能这样有规律的拆分,层次结构过于复杂,也没办法拆分。
    iOCZ
        12
    iOCZ  
       2023-07-05 11:18:43 +08:00
    1 秒都不到怎么能算慢呢
    iOCZ
        13
    iOCZ  
       2023-07-05 11:19:05 +08:00
    我们的人生是需要转圈圈的
    debuggerx
        14
    debuggerx  
       2023-07-05 11:21:49 +08:00 via Android
    数据量大的时候就应该考虑是不是不应该用 json
    lisongeee
        15
    lisongeee  
       2023-07-05 11:47:04 +08:00
    @tool2d 不需要有规律的拆分,可以按照解析的 字符数量 拆分就行

    比如每解析 10000 个 char 就 await nextTick() 一次就行
    tool2d
        16
    tool2d  
       2023-07-05 11:51:20 +08:00
    @lisongeee dark 又不是单线程,你异步拆分和楼主解析单独放在 compute 线程里,是一回事,没办法提速。

    要提速只能多线程拆分,按照 10000 个字节拆,算法上不好设计。
    duke807
        17
    duke807  
       2023-07-05 11:52:29 +08:00 via Android
    不如弃用 json 改用和 json 类似但支持二进制的 msgpack
    roundgis
        18
    roundgis  
       2023-07-05 12:13:36 +08:00 via Android
    @visper 處理 text python 並不慢 就算是慢的部分也用 c 重寫了
    icyalala
        19
    icyalala  
       2023-07-05 12:25:34 +08:00
    你要用 C/C++ 的那些库比如 simdjson ,帖子里的 24M json 也就十几 ms
    x77
        20
    x77  
       2023-07-05 12:45:13 +08:00
    - 异步解析
    - 缓存(避免重复的解析)
    - 减少 Json 的体积
    - 改进设计,不用 Json 存储巨量数据
    flyqie
        21
    flyqie  
       2023-07-05 12:53:26 +08:00 via Android
    json 真心不适合储存大量的数据。。

    大量数据用 binary 比较好办。。
    lysS
        22
    lysS  
       2023-07-05 13:44:02 +08:00
    数据够大,调 ffi 有提升
    honhon
        23
    honhon  
       2023-07-05 14:22:51 +08:00
    数据量大 json 不适合
    mxT52CRuqR6o5
        24
    mxT52CRuqR6o5  
       2023-07-05 14:26:26 +08:00 via Android
    如果结论没问题的话,应该就是 dartvm 比较菜了
    janus77
        25
    janus77  
       2023-07-05 14:27:06 +08:00
    如果是一次性,那么就一次解析完了重复使用
    如果不是一次性,该考虑换 protobuf ,大数据用 json 本来就是不合理的选择,或者改需求了,在移动端设备上是否需要执行这种复杂逻辑
    mxT52CRuqR6o5
        26
    mxT52CRuqR6o5  
       2023-07-05 14:29:33 +08:00
    还有一个问题,你说 dart 慢会「影响 UI 线程」,说明你在开发安卓
    但 nodejs 等其他语言的测试应该都是直接跑在 mac 上的
    这个性能是不太好直接比较的
    Kaiv2
        27
    Kaiv2  
       2023-07-05 15:28:43 +08:00
    Mac Air M1 2020
    Rust:
    elapsed: 149.987542ms
    elapsed: 107.580541ms
    elapsed: 105.902334ms
    elapsed: 105.940375ms
    elapsed: 105.132084ms
    elapsed: 105.841583ms
    elapsed: 105.093ms
    elapsed: 105.630708ms
    elapsed: 105.344959ms
    elapsed: 105.7735ms
    Java:
    elapsed: 263ms
    elapsed: 189ms
    elapsed: 44ms
    elapsed: 50ms
    elapsed: 41ms
    elapsed: 56ms
    elapsed: 39ms
    elapsed: 53ms
    elapsed: 39ms
    elapsed: 39ms
    Kaiv2
        28
    Kaiv2  
       2023-07-05 15:29:50 +08:00
    @Kaiv2 Java 使用的 jackson
    ljsh093
        29
    ljsh093  
       2023-07-05 15:37:07 +08:00
    @Kaiv2 #28 方便搞下 fastjson 吗
    serco
        30
    serco  
       2023-07-05 15:51:53 +08:00
    @simman serde_json 浪费了不少时间在建 struct 上,不知道具体数据类型的话,还不如 json 这种 crate 快。

    json parse 这种常规操作各个语言都有优化,差距不会特别大的,而且还要看具体做到了哪一步,是真的 parse 完建立了对应的类型,还是处在类似 pre-parsing 的状态临查询了才真的实例对应的类型。
    Kaiv2
        31
    Kaiv2  
       2023-07-05 15:52:21 +08:00
    @ljsh093
    Java: Fastjson 1.2.83
    elapsed: 349ms
    elapsed: 107ms
    elapsed: 88ms
    elapsed: 98ms
    elapsed: 79ms
    elapsed: 77ms
    elapsed: 78ms
    elapsed: 85ms
    elapsed: 111ms
    elapsed: 61ms
    Java: Fastjson2 2.0.34
    elapsed: 214ms
    elapsed: 65ms
    elapsed: 71ms
    elapsed: 62ms
    elapsed: 53ms
    elapsed: 46ms
    elapsed: 46ms
    elapsed: 46ms
    elapsed: 46ms
    elapsed: 49ms
    ljsh093
        32
    ljsh093  
       2023-07-05 16:39:02 +08:00
    @Kaiv2 #31 这样一比确实 fast😅,不过 2 版本后还算可以
    smirkcat
        33
    smirkcat  
       2023-07-05 16:41:45 +08:00
    用字节的 ast 解析引擎
    icyalala
        34
    icyalala  
       2023-07-05 16:45:43 +08:00
    @Kaiv2 再来个 C++ 的,也是 M1
    simdjson:
    18.44 ms
    16.54 ms
    16.05 ms
    16.77 ms
    16.41 ms
    yyjson:
    14.66 ms
    14.37 ms
    14.00 ms
    13.89 ms
    13.68 ms
    rapidjson:
    68.54 ms
    67.82 ms
    67.06 ms
    67.04 ms
    66.88 ms
    GiantHard
        35
    GiantHard  
       2023-07-05 17:15:46 +08:00
    @serco #30
    说得对,crate json 要比 serde_json 快一倍( AMD Ryzen 7 5800U )

    ```rs
    // build with cargo build -r
    use std::fs::File;
    use std::io::{Read};

    use serde_json::Value;
    use json;

    fn parse_json(contents: &Vec<u8>) {
    let now = std::time::Instant::now();
    let _: Value = serde_json::from_slice(&contents).unwrap();
    let elapsed = now.elapsed();
    println!("serde_json: {:?}", elapsed);
    }

    fn parse_json2(contents: &str) {
    let now = std::time::Instant::now();
    let _ = json::parse(&contents).unwrap();
    let elapsed = now.elapsed();
    println!("json: {:?}", elapsed);
    }

    fn main() {
    let contents = {
    let mut vec = Vec::new();
    // https://github.com/json-iterator/test-data/blob/master/large-file.json
    File::open("large-file.json").unwrap().read_to_end(&mut vec).unwrap();
    vec
    };
    for _ in 0..10 {
    parse_json(&contents);
    }
    for _ in 0..10 {
    // create json str from contents
    let contents = String::from_utf8(contents.clone()).unwrap();
    parse_json2(&contents);
    }
    }

    ```

    ```
    serde_json: 182.566705ms
    serde_json: 157.330185ms
    serde_json: 151.551179ms
    serde_json: 150.997928ms
    serde_json: 158.290287ms
    serde_json: 151.983899ms
    serde_json: 152.493476ms
    serde_json: 150.337436ms
    serde_json: 151.174303ms
    serde_json: 150.424319ms
    json: 71.080736ms
    json: 73.125015ms
    json: 80.531158ms
    json: 82.744729ms
    json: 79.73645ms
    json: 80.040878ms
    json: 80.30521ms
    json: 79.455428ms
    json: 79.702968ms
    json: 72.22127ms

    ________________________________________________________
    Executed in 2.42 secs fish external
    usr time 2.36 secs 0.00 micros 2.36 secs
    sys time 0.06 secs 464.00 micros 0.06 secs
    ```

    当然,你换个编程语言也可以比 serde_json 更快

    ```fs
    /// build with dotnet build -c Release
    open System.IO
    open System.Text.Json
    open System.Diagnostics

    let json = File.ReadAllText("large-file.json")

    for i in 1..10 do
    let sw = Stopwatch.StartNew()
    let jsonDocument = JsonDocument.Parse(json)
    sw.Stop()
    // print in ms
    printfn "Elapsed: %dms" sw.ElapsedMilliseconds

    printf "Done"
    ```

    ```
    Elapsed: 185ms
    Elapsed: 74ms
    Elapsed: 73ms
    Elapsed: 70ms
    Elapsed: 75ms
    Elapsed: 74ms
    Elapsed: 67ms
    Elapsed: 69ms
    Elapsed: 73ms
    Elapsed: 73ms
    Done
    ________________________________________________________
    Executed in 963.57 millis fish external
    usr time 881.76 millis 0.00 micros 881.76 millis
    sys time 86.87 millis 387.00 micros 86.48 millis
    ```
    nuk
        36
    nuk  
       2023-07-05 17:32:12 +08:00
    可以分块解析,或者自己做个匹配算法,理论上 json 解析只要不分配内存其实是很快的。那如果又要全部解析,又要快,那只能调用别的了,但是几百毫秒的这么做意义不大。
    zibber
        37
    zibber  
       2023-07-06 00:07:04 +08:00
    用 c++写个 api, 然后直接掉 c++的接口
    kingzeus
        38
    kingzeus  
       2023-07-06 09:51:28 +08:00
    1. 用 C++
    2. dart 的话,可以考虑异步 Future/Isolate
    3. 预处理,改成更加高效的数据格式
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   984 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 21:11 · PVG 05:11 · LAX 13:11 · JFK 16:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.