While testing the performance of await
, I uncovered a confounding mystery. I ran each of the following code snippets several times each in the console to filter out flukes, and took the average times of the relevant data.
(function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = O_0;
}
console.timeEnd();
})(console);
Resulting in: default: 5.322021484375ms
Next, I tried adding making it async
hronous
(async function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = O_0;
}
console.timeEnd();
})(console);
Nice! Chrome knows its stuff. Very low overhead: default: 8.712890625ms
Next, I tried adding await
.
(async function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = await O_0;
}
console.timeEnd();
})(console);
This results in 100x speed reduction: default: 724.706787109375ms
So, there must be some logical reason, right? I tried paring the types prior.
(async function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = typeof O_0 === "object" ? await O_0 : O_0;
}
console.timeEnd();
})(console);
Okay, so that is not it: default: 6.7939453125ms
So then, it must be the promise-part: checking to see if the item passed to await is a promise. That must be the culprit, am I right or am I right?
(async function(console, Promise){
"use strict";
const isPromise = Promise.prototype.isPrototypeOf.bind(Promise);
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = isPromise(O_0) ? await O_0 : O_0;
}
console.timeEnd();
})(console, Promise);
This results in: default: 7.2041015625ms
Okay, okay, let us give Chrome the benefit of the doubt. Let us assume, for a second, that they programmed await far less than perfectly.
(async function(console, Promise){
"use strict";
const isPromise = Promise.prototype.isPrototypeOf.bind(Promise.prototype);
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const isAnObject = typeof O[0] === "object" ? true : false;
const isThisAPromise = isPromise(O[0]);
O[0] = isAnObject && isThisAPromise ? await O[0] : O[0];
}
console.timeEnd();
})(console, Promise);
But even this fails to explain the poor performance of await
: default:7.85498046875ms
Okay, honestly, I give up. I would think that await
would be at least 100x faster than it is now. I cannot think of a single good reason why it would not be 100x faster in a perfect world. However, we do not live in a perfect world, so there inlies the question: how? How? How is it this slow? Is there any hope of it being any faster in the future (like maybe, say, around about 100x faster)? I am looking for facts and an objective analysis of this issue that would explain the puzzling mystery I am seeing in the above performance tests.
While testing the performance of await
, I uncovered a confounding mystery. I ran each of the following code snippets several times each in the console to filter out flukes, and took the average times of the relevant data.
(function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = O_0;
}
console.timeEnd();
})(console);
Resulting in: default: 5.322021484375ms
Next, I tried adding making it async
hronous
(async function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = O_0;
}
console.timeEnd();
})(console);
Nice! Chrome knows its stuff. Very low overhead: default: 8.712890625ms
Next, I tried adding await
.
(async function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = await O_0;
}
console.timeEnd();
})(console);
This results in 100x speed reduction: default: 724.706787109375ms
So, there must be some logical reason, right? I tried paring the types prior.
(async function(console){
"use strict";
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = typeof O_0 === "object" ? await O_0 : O_0;
}
console.timeEnd();
})(console);
Okay, so that is not it: default: 6.7939453125ms
So then, it must be the promise-part: checking to see if the item passed to await is a promise. That must be the culprit, am I right or am I right?
(async function(console, Promise){
"use strict";
const isPromise = Promise.prototype.isPrototypeOf.bind(Promise);
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const O_0 = O[0];
O[0] = isPromise(O_0) ? await O_0 : O_0;
}
console.timeEnd();
})(console, Promise);
This results in: default: 7.2041015625ms
Okay, okay, let us give Chrome the benefit of the doubt. Let us assume, for a second, that they programmed await far less than perfectly.
(async function(console, Promise){
"use strict";
const isPromise = Promise.prototype.isPrototypeOf.bind(Promise.prototype);
console.time();
var O = [1];
for (var i=0; i !== 107000; ++i) {
const isAnObject = typeof O[0] === "object" ? true : false;
const isThisAPromise = isPromise(O[0]);
O[0] = isAnObject && isThisAPromise ? await O[0] : O[0];
}
console.timeEnd();
})(console, Promise);
But even this fails to explain the poor performance of await
: default:7.85498046875ms
Okay, honestly, I give up. I would think that await
would be at least 100x faster than it is now. I cannot think of a single good reason why it would not be 100x faster in a perfect world. However, we do not live in a perfect world, so there inlies the question: how? How? How is it this slow? Is there any hope of it being any faster in the future (like maybe, say, around about 100x faster)? I am looking for facts and an objective analysis of this issue that would explain the puzzling mystery I am seeing in the above performance tests.
-
8
What is the puzzling mystery? Fulfilling promises takes time. The engine needs to allow other tasks to run. Without
await
, Chrome can concentrate on doing your task. With 107000 awaits, it's like trying to program while you have a chatty boss ing to bug you about every little thing all the damn time. – Amadan Commented Sep 7, 2018 at 11:45 -
4
I'm not sure what is the benefit of paring promises to synchronous execution? Shouldn't you rather pare
async
/await
with athen
chain? – Bergi Commented Sep 7, 2018 at 11:49 -
5
Notice that
await
does not check whether the awaited value is a promise, and continues synchronously when it is not. It always doesPromise.resolve(value)
and waits for that, at least one tick. You shouldn't be surprised that doingawait
conditionally (and in your case, never, as none of your awaited values are promises) is as fast as not doingawait
at all. – Bergi Commented Sep 7, 2018 at 11:52 -
3
I mean, it might be flippant, but it's right there in the name:
await
. Who knew that waiting takes time. :) – Amadan Commented Sep 7, 2018 at 12:06 -
2
@JackGiffin But there is a delay - any value is converted to a promise and then waited for, also allowing other concurrent promise chains to make progress. Just don't pass non-promise objects to
await
- as you demonstrated it is as fast as you expect! – Bergi Commented Sep 7, 2018 at 13:05
1 Answer
Reset to default 10You can easily observe a difference between an await
expression and lack thereof. At the very least, you are asking the engine to look at the microtask queue, possibly doing other work that happened as a result of I/O pleting. Given that, this cannot possibly be optimized into nothing.
If you truly wish to spin the CPU for a few milliseconds, don't write await
.
Here's an example. It prints 1 2 3
.
Promise.resolve().then(()=>console.log(2));
(async()=>{
console.log(1);
await undefined;
console.log(3);
})();
await undefined
is not a "do-nothing" statement. It's JavaScript's cooperative multitasking.