Skip to content

Instantly share code, notes, and snippets.

@andyvanee
Last active October 18, 2024 03:12
Show Gist options
  • Save andyvanee/d2a2de608f7eeb3358e54689083284a5 to your computer and use it in GitHub Desktop.
Save andyvanee/d2a2de608f7eeb3358e54689083284a5 to your computer and use it in GitHub Desktop.
How slow is async/await?

How slow is async/await?

TL;DR - incredibly slow!

Motivation

I was working on some "framework" type code, where it often makes sense to provide wrappers where the framework accepts a user-defined callback. The sensible default is to accept an async callback since you don't know when the user might need to do any disk/network/etc calls which require an async wrapper.

The question arose of what the cost is of supporting async callbacks rather than synchronous ones. I assumed the overhead would not be huge, but thought it was worth validatating that assumption and, boy, was I wrong!

Setup

I ran both async and sync versions of a fairly trivial function. I did 10 runs where each run called the function 1,000,000 times and then ran this test in three different environments.

Each run called the sync function (callSync), the async function with await (callAsync), and the sync function with await (callAwait).

Results

Browser

Average time for callSync: 0.630ms
Average time for callAwait: 1187.240ms
Average time for callAsync: 2835.600ms

Node.js

node index.js

Average time for callSync: 0.589ms
Average time for callAwait: 39.519ms
Average time for callAsync: 83.566ms

Bun.sh

bun run index.js

Average time for callSync: 0.706ms
Average time for callAwait: 75.163ms
Average time for callAsync: 154.645ms

Summary

I don't consider this code to be a great "benchmark" between the various platforms, but the undeniable conclusion is that async functions have a high overhead when used in "hot" code paths and should be avoided if possible.

Most framework code is not called in a hot loop like this, so the overhead is nearly always worth the flexibility, but this was not the result I was hoping for. It would be much simpler to just use async functions by default.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Async/Await</title>
<script type="module" src="/index.js"></script>
</head>
<body>
</body>
</html>
const SYM = Symbol()
function callSync() {
return SYM
}
async function callAsync() {
return SYM
}
const times = {
callSync: 0,
callAwait: 0,
callAsync: 0
}
const testCount = 10
const testIterations = 1_000_000
const perfWrapSync = (count, callback) => {
const start = performance.now()
for (let i = 0; i < count; i++) {
callback()
}
return performance.now() - start
}
const perfWrapAsync = async (count, callback) => {
const start = performance.now()
for (let i = 0; i < count; i++) {
await callback()
}
return performance.now() - start
}
for (let i = 0; i < testCount; i++) {
times.callSync += perfWrapSync(testIterations, () => callSync())
times.callAwait += await perfWrapAsync(testIterations, async () => callSync())
times.callAsync += await perfWrapAsync(testIterations, async () => await callAsync())
}
const f = (a) => a.toFixed(3)
console.log(`Average time for callSync: ${f(times.callSync / testCount)}ms`)
console.log(`Average time for callAwait: ${f(times.callAwait / testCount)}ms`)
console.log(`Average time for callAsync: ${f(times.callAsync / testCount)}ms`)
{
"name": "async-await-profile",
"version": "0.0.1",
"main": "index.js",
"type": "module",
"scripts": {
"run": "node index.js"
},
"author": "andyvanee",
"license": "MIT",
"description": ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment