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

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

  •  
  •   simman · 232 天前 · 3143 次点击
    这是一个创建于 232 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在写 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 条附言  ·  232 天前

    不好意思,上面使用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  
       232 天前
    为什么要把编译的时间也算进去?
    codehz
        2
    codehz  
       232 天前 via iPhone   ❤️ 1
    你可以考虑一下解析 json 的目的,如果可行的话,不需要先变成一个对象,而是直接从流式解析中提取需要的数据
    simman
        3
    simman  
    OP
       232 天前
    @MoYi123 go 、rust 、dart 实际是编译完后再 time 执行的。上面写的有问题。
    newmlp
        4
    newmlp  
       232 天前
    你这都把程序启动时间算上了,肯定不准啊,json 解析才能用多少时间估计 1ms 都不到
    tool2d
        5
    tool2d  
       232 天前
    纯好奇,用自己写的库解析了一下主贴里的 24M json ,竟然要 1 秒。

    发现 dart 这些库,已经很强了。
    wxf666
        6
    wxf666  
       232 天前
    你要存啥呢?用个普通数据库也不错呀。。

    比如,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  
       232 天前
    rust 比 python 还慢.谁说 rust 快的
    lisongeee
        8
    lisongeee  
       232 天前
    json 解析函数可以改成异步嘛?拆成多个子任务

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

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

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

    比如每解析 10000 个 char 就 await nextTick() 一次就行
    tool2d
        16
    tool2d  
       232 天前
    @lisongeee dark 又不是单线程,你异步拆分和楼主解析单独放在 compute 线程里,是一回事,没办法提速。

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

    大量数据用 binary 比较好办。。
    lysS
        22
    lysS  
       232 天前
    数据够大,调 ffi 有提升
    honhon
        23
    honhon  
       232 天前
    数据量大 json 不适合
    mxT52CRuqR6o5
        24
    mxT52CRuqR6o5  
       232 天前 via Android
    如果结论没问题的话,应该就是 dartvm 比较菜了
    janus77
        25
    janus77  
       232 天前
    如果是一次性,那么就一次解析完了重复使用
    如果不是一次性,该考虑换 protobuf ,大数据用 json 本来就是不合理的选择,或者改需求了,在移动端设备上是否需要执行这种复杂逻辑
    mxT52CRuqR6o5
        26
    mxT52CRuqR6o5  
       232 天前
    还有一个问题,你说 dart 慢会「影响 UI 线程」,说明你在开发安卓
    但 nodejs 等其他语言的测试应该都是直接跑在 mac 上的
    这个性能是不太好直接比较的
    Kaiv2
        27
    Kaiv2  
       232 天前
    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  
       232 天前
    @Kaiv2 Java 使用的 jackson
    ljsh093
        29
    ljsh093  
       232 天前
    @Kaiv2 #28 方便搞下 fastjson 吗
    serco
        30
    serco  
       232 天前
    @simman serde_json 浪费了不少时间在建 struct 上,不知道具体数据类型的话,还不如 json 这种 crate 快。

    json parse 这种常规操作各个语言都有优化,差距不会特别大的,而且还要看具体做到了哪一步,是真的 parse 完建立了对应的类型,还是处在类似 pre-parsing 的状态临查询了才真的实例对应的类型。
    Kaiv2
        31
    Kaiv2  
       232 天前
    @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  
       232 天前
    @Kaiv2 #31 这样一比确实 fast😅,不过 2 版本后还算可以
    smirkcat
        33
    smirkcat  
       232 天前
    用字节的 ast 解析引擎
    icyalala
        34
    icyalala  
       232 天前
    @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  
       232 天前
    @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  
       232 天前
    可以分块解析,或者自己做个匹配算法,理论上 json 解析只要不分配内存其实是很快的。那如果又要全部解析,又要快,那只能调用别的了,但是几百毫秒的这么做意义不大。
    zibber
        37
    zibber  
       232 天前
    用 c++写个 api, 然后直接掉 c++的接口
    kingzeus
        38
    kingzeus  
       231 天前
    1. 用 C++
    2. dart 的话,可以考虑异步 Future/Isolate
    3. 预处理,改成更加高效的数据格式
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2674 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 00:32 · PVG 08:32 · LAX 16:32 · JFK 19:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.