Javascript 异步函数的开销是多少

问题描述 投票:0回答:3

问题:与常规函数的 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
}

对战

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
-ing 会产生堆叠本地函数作用域的开销,而正常的事件循环在函数返回时继续,而不必将内部函数作用域推入堆栈?

这归结为一个额外的问题:在一个复杂的类层次结构中(在某处很深的地方)需要一个同步 IO 操作(见注释),理想情况下是

await
'ed。仅当该方法标记为
async
时才有可能。这又要求调用函数是
async
才能再次
await
等等。因此,在需要时,所有内容都标记为
async
await
...如何处理这种情况?

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

注2:这个问题不是关于什么是

await
async
,也不是关于它何时执行。这个问题是关于性能和语言的内部结构(即使存在多种实现,该概念可能存在固有的语义开销)。

javascript node.js asynchronous
3个回答
23
投票

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

同步与异步

函数返回一个值。

async
函数创建一个 Promise 对象以从函数返回。 Promise 对象被设置为维护异步任务的状态并处理错误或后续的链式调用。该承诺将在事件循环的下一个周期之后得到解决或拒绝。 (这有点简短,如果您想了解详细信息,请阅读规范)与简单的函数调用和返回值相比,这会产生内存和处理开销。

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

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

如果您处于一个逐个字符解析字符串的紧密循环中,您可能不希望创建一个 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 })

奔跑

→ 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

如果您想更详细地比较各种 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
    

-1
投票
有些操作您不想等待。例如,如果你想做多个XHR,同时加载多个文件,自动等待会使加载线性化,这不好


-1
投票
async/await 和 Promises 的美妙之处在于您可以混合使用它们。对于多个 XHR 请求,您可以简单地返回 Promise.all():

async function fetchPages() { return Promise.all([ fetch("this.html"), fetch("that.html") ]); } for (var page of fetchPages() { ... }
    
© www.soinside.com 2019 - 2024. All rights reserved.