Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the overhead of Javascript async functions

Tags:

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

like image 213
Simon Avatar asked Oct 24 '17 01:10

Simon


People also ask

What are JavaScript asynchronous functions?

An async function is a function declared with the async keyword, and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

What is the benefit of async await in JavaScript?

with async / await , you write less code and your code will be more maintainable than using the previous asynchronous programming methods such as using plain tasks. async / await is the newer replacement to BackgroundWorker , which has been used on windows forms desktop applications.

Does async await improve performance?

C# Language Async-Await Async/await will only improve performance if it allows the machine to do additional work.

Is JavaScript truly async?

JavaScript is a single-threaded, non-blocking, asynchronous, concurrent programming language with lots of flexibility.


1 Answers

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 
like image 106
Matt Avatar answered Oct 02 '22 04:10

Matt