Javascript异步功能的开销是多少 [英] What is the overhead of Javascript async functions
问题描述
问题:在引擎运行时中是否存在(如果需要的话,在多大程度上)将函数声明为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
,依此类推.因此,在需要时将所有内容标记为async
和await
...如何处理这种情况?
注意:请不要争论不执行任何同步操作的必要性,因为这不是重点.
注释2 :此问题与await
或async
是什么有关,也不关乎何时执行.这个问题与语言的性能和内部有关(即使存在多种实现,但该概念可能存在固有的语义开销).
与同步函数相比,异步函数具有固有的开销. 当然可以使所有内容异步,但是您可能很快就会遇到性能问题.
同步与异步
一个函数返回一个值.
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屋!