JavaScriptasynchronous函数的开销是多less?
问题 :是否有(如果是的话)在引擎运行时将计算开销声明为async
函数,并最终await
与常规函数的返回语句相比较?
async function foo() { var x = await bar(); // <--- bar() is non-blocking so await to get the return value return x; // the return value is wrapped in a Promise because of async }
与
function foo() { var x = bar(); // <--- bar() is blocking inside its body so we get the return value return new Promise(resolve => { resolve(x); }); // return a Promise manually }
背景 :
由于JavaScript(也就是Nodejs)采用的asynchronous方向,为什么默认情况下他们不认为每个函数都是asynchronous的(按照async
关键字)?
这样,人们可以决定把任何函数调用看成是一个Promise
并且扮演asynchronous的游戏,或者await
什么是必要的。
我想在函数体内await
会产生叠加本地函数作用域的开销,而正常的事件循环在函数返回时继续进行,而不必将内部函数作用域推入栈中?
这归结为一个额外的问题:在一个复杂的层次结构中(某处深处)需要一个同步的IO操作(见注),理想情况下将await
。 只有该方法标记为async
时才可能。 这反过来要求调用函数是async
,以便能够再次await
。 因此,所有标记为async
并在需要时await
…如何处理这种情况?
注意 :请不要争论不做任何同步操作的必要性,因为这不是重点。
注2 :这个问题不是关于什么是await
或async
也不是什么时候执行。 这个问题是关于性能和语言的内部(即使存在多个实现,这个概念可能存在固有的语义开销)。
与同步函数相比,asynchronous函数具有固有的开销。 尽可能使所有的asynchronous,但你可能会很快遇到性能问题。
同步与asynchronous
一个函数返回一个值。
一个async
函数创build一个Promise对象从函数返回。 Promise对象设置为维护asynchronous任务的状态,并处理错误或后续的链接调用。 承诺将在事件循环的下一个勾号之后被解决或被拒绝。 (这有点简单,如果你想要细节,请阅读规范 )与简单的函数调用和返回值相比,这既有内存也有处理开销。
量化开销是有点无用,因为大多数asynchronous函数是asynchronous的,因为他们不得不等待外部的Node.js线程来完成一些工作,通常做IO慢。 设置Promise的开销与整个操作时间相比是非常小的,特别是如果替代方法是阻止主JS线程。
另一方面,同步代码立即在主JS线程中运行。 交叉区域调度同步代码,无论是为了定时,还是为了将主JS线程的使用“限制”到下一个tick,GC和其他asynchronous任务都有机会运行。
如果你正在通过charparsing一个stringchar,你可能不想创build一个承诺,并等待它在每次迭代中解决,因为完成该过程的内存和时间要求将迅速爆炸。
另一方面,如果你所有的应用程序都在查询一个数据库并把结果转储到一个koa http响应中,那么你可能在asynchronous承诺中做了大部分事情(尽pipe在下面会有大量的同步函数来实现这一点)。
愚蠢的例子
一个人为的例子的基准,一个同步返回和解决同一同步操作的各种asynchronous方法之间的区别。
const Benchmark = require('benchmark') const Bluebird = require('bluebird') let a = 3 const asyncFn = async function asyncFn(){ a = 3 return a+2 } const cb = function(cb){ cb(null, true) } let suite = new Benchmark.Suite() suite .add('fn', function() { a = 3 return a+2 }) .add('cb', { defer: true, fn: function(deferred) { process.nextTick(()=> deferred.resolve(a+2)) } }) .add('async', { defer: true, fn: async function(deferred) { let res = await asyncFn() deferred.resolve(res) } }) .add('promise', { defer: true, fn: function(deferred) { a = 3 return Promise.resolve(a+2).then(res => deferred.resolve(res)) } }) .add('bluebird', { defer: true, fn: function(deferred) { a = 3 return Bluebird.resolve(a+2).then(res => deferred.resolve(res)) } }) // add listeners .on('cycle', event => console.log("%s", event.target)) .on('complete', function(){ console.log('Fastest is ' + this.filter('fastest').map('name')) }) .on('error', error => console.error(error)) .run({ 'async': true })
跑
→ node promise_resolve.js fn x 138,794,227 ops/sec ±1.10% (82 runs sampled) cb x 3,973,527 ops/sec ±0.82% (79 runs sampled) async x 2,263,856 ops/sec ±1.16% (79 runs sampled) promise x 2,583,417 ops/sec ±1.09% (81 runs sampled) bluebird x 3,633,338 ops/sec ±1.40% (76 runs sampled) Fastest is fn
如果您想要更详细地比较各种承诺和callback实现的性能/开销,请检查bluebirds基准testing 。
file time(ms) memory(MB) callbacks-baseline.js 154 33.87 callbacks-suguru03-neo-async-waterfall.js 227 46.11 promises-bluebird-generator.js 282 41.63 promises-bluebird.js 363 51.83 promises-cujojs-when.js 497 63.98 promises-then-promise.js 534 71.50 promises-tildeio-rsvp.js 546 83.33 promises-lvivski-davy.js 556 92.21 promises-ecmascript6-native.js 632 98.77 generators-tj-co.js 648 82.54 promises-ecmascript6-asyncawait.js 725 123.58 callbacks-caolan-async-waterfall.js 749 109.32
有一些行动,你不想等待。 例如,如果你想做几个XHR,同时加载几个文件,自动等待会使加载线性,这是不好的