Javascript异步函数的开销是多少?

Sim*_*mon 10 javascript asynchronous node.js

问题:与常规函数的return语句相比,是否存在(并且,如果是,在何种程度上)引擎运行时的计算开销以声明函数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
}
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

背景:

由于Javascript(和Nodejs)采用异步方向,为什么他们不async默认每个函数都是异步的(按照关键字)?

通过这种方式,人们可以决定将任何函数调用视为一个Promise并播放异步游戏,或者仅仅await是必要的.

我想 - await在一个函数体内创建堆叠本地函数范围的开销,而正常的事件循环在函数返回时继续,而不必将内部函数范围推送到堆栈?

这归结为一个额外的问题:在一个复杂的类层次中(深处某个地方)需要一个理想情况下需要的同步IO操作(参见注释)await.只有将该方法标记为,才有可能async.这又要求调用功能async能够await再次使用它等等.因此,所有标记asyncawait需要时...如何处理这样的情况?

注意:请不要争论不进行任何同步操作的必要性,因为这不是重点.

注2:这个问题不是关于什么是什么await或什么async时候执行.这个问题是关于性能和语言的内部结构(尽管存在多个实现,但概念可能存在固有的语义开销).

Mat*_*att 8

与同步功能相比,异步功能具有固有的开销。当然可以使所有内容异步,但是您很可能会很快遇到性能问题。

同步与异步

函数返回一个值。

一个async函数创建一个承诺对象从函数返回。设置Promise对象以维护异步任务的状态并处理错误或后续的链接调用。事件循环的下一个滴答声之后,承诺将被解决或拒绝。(这有点简短,如果需要详细信息,请阅读规范。)与简单的函数调用和返回值相比,这既有内存,也有处理开销。

但是,对开销进行量化是没有用的,因为大多数异步功能都是异步的,这是因为它们必须等待外部Node.js线程来完成某些工作,通常会做慢IO。与操作的总时间相比,设置Promise的开销非常小,尤其是如果替代方法是阻塞主JS线程的话。

另一方面,同步代码立即在JS主线程中运行。跨接区域正在调度同步代码,以用于定时或“限制”主JS线程到下一个刻度,以便GC和其他异步任务有运行的机会。

如果您处于一个用char解析字符串char的紧密循环中,则可能不想创建一个promise并等待它在每次迭代中解析,因为完成该过程的内存和时间要求将迅速爆炸。

另一方面,如果您的应用程序要做的只是查询数据库并将结果转储到koa http响应中,则您可能会在异步承诺中做大多数事情(尽管在下面仍然有很多同步功能使之发生)。

愚蠢的例子

一个人为例子的基准,同步返回和解决同一同步操作的各种异步方法之间的区别。

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 })
Run Code Online (Sandbox Code Playgroud)

? 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
Run Code Online (Sandbox Code Playgroud)

如果您想更详细地比较各种promise和回调实现的性能/开销,请同时检查bluebirds基准测试

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
Run Code Online (Sandbox Code Playgroud)