【问题标题】:What is the overhead of Javascript async functionsJavascript异步函数的开销是多少
【发布时间】:2018-04-04 16:13:16
【问题描述】:

问题:与 a常规函数的返回语句?

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 等等。因此,在需要时标记asyncawait 的所有内容......如何处理这种情况?

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

注意 2:这个问题不是关于 awaitasync 是什么,也不是关于它何时执行。这个问题是关于性能和语言内部的(尽管存在多种实现,但这个概念可能存在固有的语义开销)。

【问题讨论】:

  • 异步函数将在最早的一个事件循环滴答之后执行。这个问题不是stackoverflow的好格式
  • 我不明白我的问题有什么问题。你能详细说明@AndyRay
  • 您可以根据需要异步处理每个函数。 await 也适用于非承诺值。 “如何处理这种情况?” 没有新的方法来处理这种情况。 async/await 只是承诺的语法糖。在处理 Promise 时,您会做同样的事情。
  • "bar() 在其体内阻塞" - 这不起作用。即使async functions 仍然是异步的——非阻塞的,这就是 node.js 中并发的全部意义。
  • 为什么他们不认为每个函数默认都是异步的?” - 因为不是每个函数都应该返回一个承诺。如果你的意思是它应该只是在引擎盖下“阻塞”,就像到处都有一个awaitthat would be an absolutely horrible idea

标签: javascript node.js asynchronous


【解决方案1】:

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

同步与异步

函数返回一个值。

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

量化开销有点没用,因为大多数异步函数都是异步的,因为它们必须等待外部 Node.js 线程完成一些工作,通常执行缓慢的 IO。与操作的总时间相比,设置 Promise 的开销非常小,尤其是在阻塞主 JS 线程的情况下。

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

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

另一方面,如果您的应用程序所做的只是 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

如果您想更详细地比较各种 Promise 和回调实现的性能/开销,还可以查看 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

【讨论】:

  • 回答重点,这在这件事上很少见!顺便说一句,我正在生成 Nodejs 进程(是的一个新进程)作为命令行实用程序,因此阻塞事实是一种预期行为。它不用作服务器来处理多个请求,也不并行处理其他任何事情。这就是为什么开销(尽管如您所展示的那样只使用一次的话是最小的)如果全部使用可能会产生更深的影响。
  • 值得将直接异步函数调用添加到基准let res = asyncFn(); deferred.resolve(res);
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 这与async 无关,您可以在函数签名中不使用async 运行相同的代码。如果你有await Promise.all(...),情况会有所不同。
    猜你喜欢
    • 2019-09-09
    • 2021-07-26
    • 1970-01-01
    • 2021-12-20
    • 2012-12-12
    • 1970-01-01
    • 2011-11-23
    • 2017-09-27
    • 1970-01-01
    相关资源
    最近更新 更多