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('.')
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
有性能差异可以理解,但是这个性能差异过大,请问各位是我的写法有问题,还是其它原因导致的呢?
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 |
2
raptium 2021-07-16 20:52:25 +08:00 via iPhone
一个串行 一个并行?
|
3
muzuiget 2021-07-16 20:54:00 +08:00
为什么拿 IO 来测试,IO 时间本来就是不确定的。
再说,为什么你会有 callback 和 promise 性能问题,这两东西语法上是可以互转的,就像你那个 util.promisify 。 一般来说,node 会把所有事件处理完毕就退出,所以你的代码,最慢那次 IO 操作就是程序的运行时间。 |
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'不好吗 |
5
seki 2021-07-16 21:01:53 +08:00
async 和 forEach 不搭,改成
await Promise.all(files.map(async (file) => {}))) 试试看 |
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 total ❯ 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 |
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 |
8
Mitt 2021-07-18 09:21:43 +08:00
@myqoo #7 这明显是你的问题了, 性能有差距也不会差这么多,你都差了 80 多倍了,而且引入随机数产生的不确定性更能被采纳为性能测试指标。
|
10
Mitt 2021-07-18 09:26:59 +08:00
@myqoo #7 https://onecompiler.com/javascript/3x5s8ss8k 在线跑差分的话,1e6 次性能差距也就是几毫秒,几乎是可以忽略的,因为你实际场景根本不会遇到这种超大量的 promise 混杂的情况,纯计算受影响的也只是线程切换,不会有这种开一大堆 promise 去计算的,这个是纯粹的开销,不符合实际场景的。
|
11
myqoo 2021-07-18 10:43:54 +08:00
@Mitt 即使单次随机数耗时不固定,但在大量次数下是稳定的,你可以试试取消 await 部分,纯粹计算随机数两者用时几乎是相等的。事实上 await 就是差那么多,甚至不止 80 倍。可以看 js 引擎源码,await 实现开销很大的,比传统语言的纤程开销大很多。
|
12
myqoo 2021-07-18 10:48:59 +08:00
@Mitt 开 promise 去计算的情况是有的,当然是代码写的不够好。比如有些函数 99.99% 情况只是计算,极小情况可能会用 await 调用一个异步 API,但 await 必须放在 async 函数里,所有每次调这个函数都是在创建 promise 。
|
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 了。 |
14
Mitt 2021-07-18 13:37:39 +08:00
@myqoo #13 异步开销是有的没错,但是我更倾向于你的测试方式不切实际,用超短程测试方式当然会显著增加额外开销这是必然的,但是实际应用下几乎不可能会有人写这样的代码,首先你 await 对象就是 non-promise,没人会这么干,而且你这种场景是很容易被“编译器”优化的,用空跑来对比的方式其实是完全舍弃掉了 async/await 的优势,仅仅是单把额外开销拿出来放大了而已,你的代码越常规这点开销越不显眼的,不然这个测试方式不仅是 js,任何语言的优势都可以被测出来无限的额外开销。
|
15
BaiLinfeng 2021-07-18 14:11:31 +08:00
我居然看不懂
|
16
myqoo 2021-07-18 14:32:01 +08:00
@Mitt await 一个 non-promise 是很常见的。比如一个 async 函数再调用 IO 之前先判断内存缓存,存在就直接返回了。但调用方仍然会用 await 去执行这个函数,相当于 await non-promise 。而且不知为什么目前所有 JS 引擎都不会优化这种调用,所以才有上述那个 gist 的测试案例。
|
17
dfkjgklfdjg 2021-07-19 09:45:07 +08:00
为啥我觉得是 forEach+await 的问题,每次都停下来等了
|
18
zhuweiyou 2021-07-19 10:24:27 +08:00
1. util.promisify 移到方法体外
2. forEach + await 是谁教你的... |
19
Austaras 2021-07-19 14:55:13 +08:00
@myqoo 你等于在问
``` async function foo() {} await foo() ``` 不是即时执行的,这就是 js 把 async 和 promise 绑定带来的问题,一个 async 函数,无论怎样返回的东西都是一个 promise,所以一定要在下一个 microtask 里执行 |
20
kebyn OP @seki async 和 forEach 不搭,这个没有考虑到,但是修改后,性能没有很大的改进
@maokai 去除 util.promisify 差距也是蛮大的 @myqoo 目前看来就是 promise 的开销问题 https://imgur.com/daBglGj |
21
CokeMine 2021-07-19 17:19:54 +08:00
async 和 forEach 一起写一般 IDE 会有提示吧。
不是很清楚,试试楼上的写法再测一下 |
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 会有一些奇淫技巧的缓存优化,所以要测的话就测语法本身的计算性能,看语法糖是否会带来额外的计算性能损耗,或者是否反而提升了计算效率。 |
24
HB9527 2021-08-03 17:57:00 +08:00
重启电脑,先运行 callback.js 再运行 promise.js 试试,vfs cache~~
|