Javascript异步功能的开销是多少 [英] What is the overhead of Javascript async functions

查看:100
本文介绍了Javascript异步功能的开销是多少的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:在引擎运行时中是否存在(如果需要的话,在多大程度上)将函数声明为async并最终与await相比的计算开销?常规函数的return语句?

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(和Node.js)采取异步方式,为什么他们默认情况下不将每个函数都视为异步(按照async关键字)?

这样,人们可以决定将任何函数调用都视为Promise并玩异步游戏,或者只是await必要的东西.

我想函数内部的await -ing会产生局部函数作用域堆栈的开销,而正常事件循环会在函数返回时继续进行,而不必将内部函数作用域推入堆栈?

这归结为一个额外的问题:在一个复杂的类层次结构中(某个地方很深),需要一个同步IO操作(请参见注释),最好进行await处理.仅当该方法标记为async时才有可能.依次需要调用函数为async才能再次await,依此类推.因此,在需要时将所有内容标记为asyncawait ...如何处理这种情况?

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

注释2 :此问题与awaitasync是什么有关,也不关乎何时执行.这个问题与语言的性能和内部有关(即使存在多种实现,但该概念可能存在固有的语义开销).

解决方案

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

同步与异步

一个函数返回一个值.

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

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

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

如果您正处于一个严密的循环中,一个字符一个字符地解析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 })

运行

→ 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基准/各种promise和回调实现的开销.

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

The question: Is there, (and if yes, to what extent) a computational overhead in the engine runtime to declare a function as async and to eventually await as compared to a regular function's return statement ?

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
}

Versus

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
}

Context:

Due to the asynchronous direction taken by Javascript (and i.e. Nodejs), why did they not consider every function to be asynchronous (as per async keyword) by default ?

This way, people could just decide to treat any function call as a Promise and play the asynchronous game, or just await what is necessary.

I suppose that await-ing within a function body creates the overhead of stacking the local function's scope whereas the normal event loop proceeds when the function returns and does not have to push the inner function scope to the stack ?

This comes down to a bonus question : in a complex hyerarchy of classes that (somewhere deep) requires one synchronous IO operation (see note) that would ideally be await'ed. It is only possible if that method is marked as async. Which in turn required the calling function to be async to be able to await it again and so forth. Thus, everything marked async and await when needed... How to deal with such a scenario ?

Note: Please do not argue about the necessity of not-doing any sync operations as this is not the point.

Note 2: This question is not about what is await or async nor when it executes. This question is about performance and the internals of the language (even though multiple implementations exist, there may be a inherent semantic overhead to the concept).

解决方案

An async function has inherent overhead compared to a synchronous function. It's certainly possible to make everything async but you would likely run into performance issues pretty quickly.

Sync vs Async

A function returns a value.

An async function creates a Promise object to return from the function. The Promise object is setup to maintain the state of the asynchronous task and handle errors or subsequent chained calls. The promise will be resolved or rejected after the next tick of the event loop. (That's a bit brief, read the the spec if you want detail) This has both a memory and processing overhead compared to a simple function call and return value.

Quantifying the overhead is a bit useless though, as most async functions are async due to them having to wait for an external Node.js thread to complete some work, normally doing slow IO. The overhead in setting up the Promise is pretty minimal compared to the overall time of the operation, especially if the alternative is to block the main JS thread.

Synchronous code on the other hand, runs immediately in the main JS thread. The crossover area is scheduling synchronous code, either for timing or for "throttling" the use of the main JS thread onto the next tick so GC and other async tasks get a chance to run.

If you're in a tight loop parsing a string char by char, you probably don't want to be creating a promise and waiting for it to resolve on each iteration as the memory and time requirements to complete the process will explode quickly.

On the other hand, if all your app does is query a database and dump the results to a koa http response then your likely doing most things in an async promise (although underneath there will still be a lot of synchronous functions making that happen).

Silly Example

A benchmark of a contrived example, the difference between a sync return and various async methods of resolving the same synchronous operation.

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

→ 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

Also check bluebirds benchmarks if you want a more detailed comparison of the performance/overhead of the various promise and callback implementations.

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

这篇关于Javascript异步功能的开销是多少的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆