Javascript异步函数的开销是多少

时间:2017-10-24 01:21:54

标签: javascript node.js asynchronous

问题:是否存在,(以及是否,在多大程度上)引擎运行时的计算开销将函数声明为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)采用异步方向,为什么他们默认不将每个函数都视为异步(根据async关键字)?

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

我认为函数体中的await会产生堆叠局部函数范围的开销,而正常的事件循环会在函数返回时继续,而不必推送内部函数范围到堆栈?

这归结为一个额外的问题:在一个复杂的类层次中(某处很深)需要一个同步IO操作(见注释),理想情况下是await。只有将该方法标记为async时才有可能。这反过来要求调用函数async能够再次await等等。因此,在需要时标记为asyncawait的所有内容......如何处理此类情景?

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

注意2 :此问题与awaitasync的内容无关,也与执行时无关。这个问题是关于性能和语言的内部结构(尽管存在多个实现,但概念可能存在固有的语义开销)。

3 个答案:

答案 0 :(得分:4)

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

同步与异步

函数返回一个值。

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

虽然量化开销有点无用,因为大多数异步函数都是异步的,因为它们必须等待外部Node.js线程完成一些工作,通常做缓慢的IO。与操作的总体时间相比,设置Promise的开销非常小,特别是如果替代方法是阻止主JS线程。

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

如果您正在通过char解析字符串char的紧密循环,您可能不希望创建一个承诺并等待它在每次迭代时解析,因为完成该过程的内存和时间要求将会爆炸很快。

另一方面,如果您的所有应用都是query a database并将结果转储到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 })

运行

→ 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

如果您想要更详细地比较各种承诺和回调实现的性能/开销,请检查bluebirds benchmarks

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

答案 1 :(得分:0)

有些行动你不想等待。例如,如果你想做几个XHR,同时加载几个文件,自动等待会使加载线性,这是不好的

答案 2 :(得分:0)

async / await和Promises的美妙之处在于你可以混合使用它们。对于多个XHR请求,您只需返回Promise.all():

async function fetchPages() {
    return Promise.all([
        fetch("this.html"),
        fetch("that.html")
    ]);
}

for (var page of fetchPages() { ... }