Skip to content

AsyncLocalStorage kills 97% of performance in an async environment #34493

@danielgindi

Description

@danielgindi
  • Version: 12.18.3
  • Platform: macos
  • Subsystem: async_hooks

What steps will reproduce the bug?

const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();

let fn = async () => /test/.test('test');

let runWithExpiry = async (expiry, fn) => {
    let iterations = 0;
    while (Date.now() < expiry) {
        await fn();
        iterations++;
    }
    return iterations;
};

(async () => {

    console.log(`Performed ${await runWithExpiry(Date.now() + 100, fn)} iterations to warmup`);

    asyncLocalStorage.run({}, () => {});
    let withAls = await runWithExpiry(Date.now() + 1000, fn);
    console.log(`Performed ${withAls} iterations (with ALS enabled)`);

    asyncLocalStorage.disable();
    let withoutAls = await runWithExpiry(Date.now() + 1000, fn);
    console.log(`Performed ${withoutAls} iterations (with ALS disabled)`);

    console.log('ALS penalty: ' + Math.round((1 - (withAls / withoutAls)) * 10000) / 100 + '%');
})();

Output:

Performed 180407 iterations to warmup
Performed 205741 iterations (with ALS enabled)
Performed 6446728 iterations (with ALS disabled)
ALS penalty: 96.8%

How often does it reproduce? Is there a required condition?

In any case that await is used.

What is the expected behavior?

Should be around 10% penalty.

What do you see instead?

I see 97% reduction in performance.

Additional information

I've played in the past with a Zone polyfill of my own (zone-polyfill), and at the beginning I tried using the stack traces that's generated in latest V8 versions, where they keep their context after await calls.
Combining that with the basic technique to track context, I was able to track the zone but due to the string nature of the stack traces - I had to map them in the memory but there was no WeakMap available. Using a NAN solution I got to about 15% penalty.
Now assuming that these capabilities are available on the c++ side and much more natively, with the option to directly leverage the compiler instead of generating stack traces to query for info - I'd assume max 10% penalty with a native implementation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    async_hooksIssues and PRs related to the async hooks subsystem.performanceIssues and PRs related to the performance of Node.js.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions