-
Notifications
You must be signed in to change notification settings - Fork 29.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Experiment] async_hooks: optimize fast-path promise hook for ALS #34512
[Experiment] async_hooks: optimize fast-path promise hook for ALS #34512
Conversation
ed4df22
to
bfe3a68
Compare
Also, not sure if this was inspired by this issue, but I think you can add: |
Remove unnecessary native-to-JS code switches in fast-path for PromiseHooks. Those switches happen even if a certain type of hook (say, before) is not installed, which may lead to sub-optimal performance in the AsyncLocalStorage scenario, i.e. when there is only an init hook.
bfe3a68
to
1274aad
Compare
@addaleax |
Here are the results of
As expected, there is an improvement in the cc @nodejs/async_hooks |
@addaleax many thanks for the quick review and helpful comments! |
BTW I've just tried to replace static void FastPromiseHook(PromiseHookType type, Local<Promise> promise,
Local<Value> parent) {
Local<Context> context = promise->CreationContext();
Environment* env = Environment::GetCurrent(context);
if (env == nullptr) return;
if (type == PromiseHookType::kInit) {
promise->Set(context, env->async_id_symbol(),
v8::Number::New(v8::Isolate::GetCurrent(), 42)).FromJust();
}
} On
With the dummy PromiseHook:
As you may see, there is a slight improvement (which has to be properly measured in multiple runs, but let's assume it's ~20-25% for now), but the main cost seems to be associated with the PromiseHook being installed in V8. Are there any optimizations that could be made in V8's PromiseHook implementation? I'm not familiar with V8 internals, so I may be wrong here. Maybe collaborators who are good at V8 can provide any inputs here. |
@puzpuzpuz Trying to reproduce your results, during profiling there’s two few things that are noticeable:
Generally, reducing the # of calls to |
@addaleax As for the V8 calls made within the dummy hook, that's a good finding. Unfortunately, I don't see any workarounds that would allow us to avoid |
Stack trace capturing currently accounts for 40 % of the benchmark running time. Always throwing the same exception object removes that overhead and lets the benchmark be more focused on what it is supposed to measure. Refs: nodejs#34512 (comment)
@addaleax I'd like to do a follow-up on further PromiseHook optimizations topic. I could create a separate GH issue, but for now I'm fine with an ad-hoc discussion here. What I'm thinking of is using With this approach we could assign async id on native side and then pass it into the To implement this approach we could expand count of internal fields to Update. It appears that we should be fine with a single internal field, so please ignore the above question. |
@puzpuzpuz Yeah, that should work, we just need to be very careful about not accidentally reading the async id and interpreting it as a The place where the internal field count is configured is here: Line 1325 in 62198d2
|
@addaleax Yes, fast/slow path hooks interop has to be kept. I'm going to submit a draft experiment PR to follow-up on the idea. And thanks for pointing me at the configuration file. |
I'm trying to understand something here. V8 knows how to generate a stack trace that considers As I noted in the issue, I achieved a much more performant ALS (actually Zone) using a JS only technique that did basically that. It generated a stack trace when querying the |
v8 is able to show the origin of a ALS doesn't has this limitation, it follows all async transactions signaled by async hooks - and this comes with a cost. |
That's right. But my Zone implementation filled the rest by wrapping callback functions and patching Promises, much like Angular's polyfill did. |
Remove unnecessary native-to-JS code switches in fast-path for PromiseHooks. Those switches happen even if a certain type of hook (say, before) is not installed, which may lead to sub-optimal performance in the AsyncLocalStorage scenario, i.e. when there is only an init hook. PR-URL: #34512 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: David Carlier <[email protected]>
Landed in 53870dd |
@danielgindi probably it makes sense to continue the conversation in #34493. |
@puzpuzpuz Could you run the benchmark snippet in #34493 on your PR and put the results here? |
@danielgindi sure. Already did that. |
Remove unnecessary native-to-JS code switches in fast-path for PromiseHooks. Those switches happen even if a certain type of hook (say, before) is not installed, which may lead to sub-optimal performance in the AsyncLocalStorage scenario, i.e. when there is only an init hook. PR-URL: #34512 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: David Carlier <[email protected]>
Remove unnecessary native-to-JS code switches in fast-path for PromiseHooks. Those switches happen even if a certain type of hook (say, before) is not installed, which may lead to sub-optimal performance in the AsyncLocalStorage scenario, i.e. when there is only an init hook. PR-URL: #34512 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: David Carlier <[email protected]>
Stack trace capturing currently accounts for 40 % of the benchmark running time. Always throwing the same exception object removes that overhead and lets the benchmark be more focused on what it is supposed to measure. Refs: #34512 (comment) PR-URL: #34523 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
Remove unnecessary native-to-JS code switches in fast-path for PromiseHooks. Those switches happen even if a certain type of hook (say, before) is not installed, which may lead to sub-optimal performance in the AsyncLocalStorage scenario, i.e. when there is only an init hook. PR-URL: #34512 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: David Carlier <[email protected]>
Native side of fast-path promise hook was not calling JS fastPromiseHook function when there were no async ids previously assigned to the promise. Because of that already created promises could not get id assigned in situations when an async hook without a before listener function is enabled after their creation. As the result executionAsyncId could return wrong id when called within promise's .then(). Refs: nodejs#34512
Native side of fast-path promise hook was not calling JS fastPromiseHook function when there were no async ids previously assigned to the promise. Because of that already created promises could not get id assigned in situations when an async hook without a before listener function is enabled after their creation. As the result executionAsyncId could return wrong id when called within promise's .then(). Refs: #34512 PR-URL: #34548 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
Stack trace capturing currently accounts for 40 % of the benchmark running time. Always throwing the same exception object removes that overhead and lets the benchmark be more focused on what it is supposed to measure. Refs: #34512 (comment) PR-URL: #34523 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
Native side of fast-path promise hook was not calling JS fastPromiseHook function when there were no async ids previously assigned to the promise. Because of that already created promises could not get id assigned in situations when an async hook without a before listener function is enabled after their creation. As the result executionAsyncId could return wrong id when called within promise's .then(). Refs: #34512 PR-URL: #34548 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
Native side of fast-path promise hook was not calling JS fastPromiseHook function when there were no async ids previously assigned to the promise. Because of that already created promises could not get id assigned in situations when an async hook without a before listener function is enabled after their creation. As the result executionAsyncId could return wrong id when called within promise's .then(). Refs: #34512 PR-URL: #34548 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
Stack trace capturing currently accounts for 40 % of the benchmark running time. Always throwing the same exception object removes that overhead and lets the benchmark be more focused on what it is supposed to measure. Refs: #34512 (comment) PR-URL: #34523 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
Stack trace capturing currently accounts for 40 % of the benchmark running time. Always throwing the same exception object removes that overhead and lets the benchmark be more focused on what it is supposed to measure. Refs: #34512 (comment) PR-URL: #34523 Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Andrey Pechkurov <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
This experiment attempts to remove unnecessary native-to-JS code switches in fast-path for PromiseHooks introduced in #32891. Those switches happen even if certain type of hook (say,
before
) is not installed, which may lead to sub-optimal performance in theAsyncLocalStorage
scenario (as ALS uses onlyinit
hook).Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes