Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
363 views
in Technique[技术] by (71.8m points)

node.js - What is the overhead of Javascript async functions

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).

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...