callback 和 promise 性能差距疑问 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
kebyn
V2EX    Node.js

callback 和 promise 性能差距疑问

  •  1
     
  •   kebyn 2021-07-16 20:27:14 +08:00 5875 次点击
    这是一个创建于 1551 天前的主题,其中的信息可能已经有所发展或是发生改变。

    callback

    import * as fs from 'fs'; import * as path from 'path'; function list_dir(dir: string) { fs.readdir(dir, 'utf-8', (err, files) => { files.forEach(file => { file = path.join(dir, file) fs.stat(file, (err, stat) => { if (stat.isDirectory()) { list_dir(file) } if (stat.isFile()) { file // console.log("%O", file); } }) }); }) } list_dir('.') 

    promise

    import * as util from 'util'; import * as fs from 'fs'; import * as path from 'path'; async function list_dir(dir: string) { const readdir = util.promisify(fs.readdir); const stat = util.promisify(fs.stat); const files = await readdir(dir, 'utf-8') files.forEach(async file => { file = path.join(dir, file) const state = await stat(file) if (state.isDirectory()) { await list_dir(file) } if (state.isFile()) { file // console.log("%O", file); } }) } list_dir('.') 

    性能测试结果

    node /workspaces/typescript $ time node promise.js && time node callback.js real 0m1.140s user 0m1.136s sys 0m1.292s real 0m0.377s user 0m0.368s sys 0m0.534s node /workspaces/typescript $ time node promise.js && time node callback.js real 0m1.062s user 0m1.132s sys 0m1.184s real 0m0.538s user 0m0.470s sys 0m0.784s node /workspaces/typescript $ time node promise.js && time node callback.js real 0m1.194s user 0m1.221s sys 0m1.308s real 0m0.436s user 0m0.393s sys 0m0.651s node /workspaces/typescript $ time node promise.js && time node callback.js real 0m1.024s user 0m1.165s sys 0m1.027s real 0m0.416s user 0m0.313s sys 0m0.653s node /workspaces/typescript $ nodejs --version v16.3.0 node /workspaces/typescript $ tsc --version Version 4.3.5 

    有性能差异可以理解,但是这个性能差异过大,请问各位是我的写法有问题,还是其它原因导致的呢?

    24 条回复    2021-08-03 17:57:00 +08:00
    maokai
        1
    maokai  
       2021-07-16 20:48:19 +08:00
    node --version
    v14.17.3

    time node /tmp/promise.js && time node /tmp/callback.js
    node /tmp/promise.js 0.66s user 0.84s system 169% cpu 0.887 total
    node /tmp/callback.js 0.42s user 1.13s system 192% cpu 0.806 total
    time node /tmp/promise.js && time node /tmp/callback.js
    node /tmp/promise.js 0.61s user 0.92s system 169% cpu 0.899 total
    node /tmp/callback.js 0.41s user 0.84s system 156% cpu 0.804 total
    time node /tmp/promise.js && time node /tmp/callback.js
    node /tmp/promise.js 0.46s user 1.03s system 168% cpu 0.886 total
    node /tmp/callback.js 0.30s user 0.76s system 134% cpu 0.783 total

    time node /tmp/callback.js&& time node /tmp/promise.js
    node /tmp/callback.js 0.44s user 0.81s system 160% cpu 0.772 total
    node /tmp/promise.js 0.64s user 0.89s system 170% cpu 0.898 total
    time node /tmp/callback.js&& time node /tmp/promise.js
    node /tmp/callback.js 0.39s user 0.87s system 163% cpu 0.776 total
    node /tmp/promise.js 0.58s user 0.92s system 168% cpu 0.885 total
    time node /tmp/callback.js&& time node /tmp/promise.js
    node /tmp/callback.js 0.36s user 0.93s system 166% cpu 0.777 total
    node /tmp/promise.js 0.61s user 0.90s system 170% cpu 0.884 total
    raptium
        2
    raptium  
       2021-07-16 20:52:25 +08:00 via iPhone
    一个串行 一个并行?
    muzuiget
        3
    muzuiget  
       2021-07-16 20:54:00 +08:00
    为什么拿 IO 来测试,IO 时间本来就是不确定的。

    再说,为什么你会有 callback 和 promise 性能问题,这两东西语法上是可以互转的,就像你那个 util.promisify 。

    一般来说,node 会把所有事件处理完毕就退出,所以你的代码,最慢那次 IO 操作就是程序的运行时间。
    littlepanzh
        4
    littlepanzh  
       2021-07-16 20:56:11 +08:00 via iPhone
    第一个,你这是 typescript,tsconfig 里 target 用的是什么呢?如果不是 esnext 的话用 async/await 会有很多额外开销。

    第二个,什么版本的 nodejs ? 14+就不要用 utils.promisfy 了,import fs from 'fs/promises'不好吗
    seki
        5
    seki  
       2021-07-16 21:01:53 +08:00
    async 和 forEach 不搭,改成

    await Promise.all(files.map(async (file) => {}))) 试试看
    maokai
        6
    maokai  
       2021-07-16 21:06:28 +08:00
    哦,对了,你需要把两个 util.promisify 拿到 list_dir 外面来,这样测出的结果差距就小一些了:

    time node /tmp/promise.js && sleep 1 && time node /tmp/callback.js
    node /tmp/promise.js 0.45s user 0.75s system 142% cpu 0.837 total
    node /tmp/callback.js 0.58s user 0.99s system 196% cpu 0.800 total
    time node /tmp/promise.js && sleep 1 && time node /tmp/callback.js
    node /tmp/promise.js 0.53s user 0.98s system 175% cpu 0.855 total
    node /tmp/callback.js 0.38s user 1.00s system 173% cpu 0.794 otal
    time node /tmp/promise.js && sleep 1 && time node /tmp/callback.js
    node /tmp/promise.js 0.55s user 0.98s system 183% cpu 0.835 total
    node /tmp/callback.js 0.52s user 0.86s system 173% cpu 0.797 total

    time node /tmp/callback.js && sleep 1 && time node /tmp/promise.js
    node /tmp/callback.js 0.48s user 0.90s system 173% cpu 0.801 total
    node /tmp/promise.js 0.63s user 1.02s system 192% cpu 0.857 total
    time node /tmp/callback.js && sleep 1 && time node /tmp/promise.js
    node /tmp/callback.js 0.41s user 0.98s system 172% cpu 0.807 total
    node /tmp/promise.js 0.59s user 0.89s system 172% cpu 0.857 total
    time node /tmp/callback.js && sleep 1 && time node /tmp/promise.js
    node /tmp/callback.js 0.42s user 1.05s system 184% cpu 0.798 total
    node /tmp/promise.js 0.56s user 0.95s system 177% cpu 0.848 total
    myqoo
        7
    myqoo  
       2021-07-17 23:57:17 +08:00
    await 性能非常非常差,注重性能的场合尽量回避。不过在 IO 的场合不明显,偏向纯计算的时候就非常明显了。

    这里有个测试 https://gist.github.com/EtherDream/52649e4939008e149d0cb3a944c055b7

    ```js
    async function pending() {
    return 11
    }

    function mayPending() {
    if (Math.random() < 0.001) {
    return pending()
    }
    return 22
    }

    async function main() {
    console.time('s1')
    for (let i = 0; i < 1e6; i++) {
    const val = await mayPending()
    }
    console.timeEnd('s1')


    console.time('s2')
    for (let i = 0; i < 1e6; i++) {
    const ret = mayPending()
    const val = ret instanceof Promise ? await ret : ret
    }
    console.timeEnd('s2')
    }

    main()
    ```

    s1: 1715.09521484375 ms
    s2: 20.174072265625 ms
    Mitt
        8
    Mitt  
       2021-07-18 09:21:43 +08:00
    @myqoo #7 这明显是你的问题了, 性能有差距也不会差这么多,你都差了 80 多倍了,而且引入随机数产生的不确定性更能被采纳为性能测试指标。
    Mitt
        9
    Mitt  
       2021-07-18 09:21:58 +08:00
    @Mitt #8 更能 => 更不能
    Mitt
        10
    Mitt  
       2021-07-18 09:26:59 +08:00
    @myqoo #7 https://onecompiler.com/Javascript/3x5s8ss8k 在线跑差分的话,1e6 次性能差距也就是几毫秒,几乎是可以忽略的,因为你实际场景根本不会遇到这种超大量的 promise 混杂的情况,纯计算受影响的也只是线程切换,不会有这种开一大堆 promise 去计算的,这个是纯粹的开销,不符合实际场景的。
    myqoo
        11
    myqoo  
       2021-07-18 10:43:54 +08:00
    @Mitt 即使单次随机数耗时不固定,但在大量次数下是稳定的,你可以试试取消 await 部分,纯粹计算随机数两者用时几乎是相等的。事实上 await 就是差那么多,甚至不止 80 倍。可以看 js 引擎源码,await 实现开销很大的,比传统语言的纤程开销大很多。
    myqoo
        12
    myqoo  
       2021-07-18 10:48:59 +08:00
    @Mitt 开 promise 去计算的情况是有的,当然是代码写的不够好。比如有些函数 99.99% 情况只是计算,极小情况可能会用 await 调用一个异步 API,但 await 必须放在 async 函数里,所有每次调这个函数都是在创建 promise 。
    myqoo
        13
    myqoo  
       2021-07-18 10:57:12 +08:00
    @Mitt 可以试试这个:

    console.time('s')
    for (let i = 0; i < 10000; i++) {
    await i
    }
    console.timeEnd('s')

    执行 1 万次 await 耗时差不多 20ms 了。
    Mitt
        14
    Mitt  
       2021-07-18 13:37:39 +08:00
    @myqoo #13 异步开销是有的没错,但是我更倾向于你的测试方式不切实际,用超短程测试方式当然会显著增加额外开销这是必然的,但是实际应用下几乎不可能会有人写这样的代码,首先你 await 对象就是 non-promise,没人会这么干,而且你这种场景是很容易被“编译器”优化的,用空跑来对比的方式其实是完全舍弃掉了 async/await 的优势,仅仅是单把额外开销拿出来放大了而已,你的代码越常规这点开销越不显眼的,不然这个测试方式不仅是 js,任何语言的优势都可以被测出来无限的额外开销。
    BaiLinfeng
        15
    BaiLinfeng  
       2021-07-18 14:11:31 +08:00
    我居然看不懂
    myqoo
        16
    myqoo  
       2021-07-18 14:32:01 +08:00
    @Mitt await 一个 non-promise 是很常见的。比如一个 async 函数再调用 IO 之前先判断内存缓存,存在就直接返回了。但调用方仍然会用 await 去执行这个函数,相当于 await non-promise 。而且不知为什么目前所有 JS 引擎都不会优化这种调用,所以才有上述那个 gist 的测试案例。
    94
        17
    94  
       2021-07-19 09:45:07 +08:00
    为啥我觉得是 forEach+await 的问题,每次都停下来等了
    zhuweiyou
        18
    zhuweiyou  
       2021-07-19 10:24:27 +08:00
    1. util.promisify 移到方法体外
    2. forEach + await 是谁教你的...
    Austaras
        19
    Austaras  
       2021-07-19 14:55:13 +08:00
    @myqoo 你等于在问
    ```
    async function foo() {}

    await foo()
    ```
    不是即时执行的,这就是 js 把 async 和 promise 绑定带来的问题,一个 async 函数,无论怎样返回的东西都是一个 promise,所以一定要在下一个 microtask 里执行
    kebyn
        20
    kebyn  
    OP
       2021-07-19 15:00:52 +08:00
    @seki async 和 forEach 不搭,这个没有考虑到,但是修改后,性能没有很大的改进
    @maokai 去除 util.promisify 差距也是蛮大的
    @myqoo 目前看来就是 promise 的开销问题
    https://imgur.com/daBglGj
    CokeMine
        21
    CokeMine  
       2021-07-19 17:19:54 +08:00
    async 和 forEach 一起写一般 IDE 会有提示吧。
    不是很清楚,试试楼上的写法再测一下
    libook
        22
    libook  
       2021-07-19 18:54:21 +08:00
    我从 ES6 正式发布之后就一直在关注 V8 的 promise 性能情况,V8 官方有个测试用例 https://github.com/v8/promise-performance-tests
    你可以 clone 跑一下看看,涵盖了原生 promise 、async/await 和主流的 promise 库的性能测试。我是见证了从一开始被 bluebird 虐逐渐到完爆 bluebird 。不过这个用例里面没有 callback,你可以参考其他的例子自己写一个。

    这种测试最好不要引入 fs,因为文件系统是由操作系统管理调度的,有些操作系统比如 Linux 会有一些奇淫技巧的缓存优化,所以要测的话就测语法本身的计算性能,看语法糖是否会带来额外的计算性能损耗,或者是否反而提升了计算效率。
    TomVista
        23
    TomVista  
       2021-07-20 09:28:26 +08:00
    @Austaras 这是正确答案
    HB9527
        24
    HB9527  
       2021-08-03 17:57:00 +08:00
    重启电脑,先运行 callback.js 再运行 promise.js 试试,vfs cache~~
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4814 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 30ms UTC 09:48 PVG 17:48 LAX 02:48 JFK 05:48
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86