Skip to content
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

MakeCallback is very slow #24

Open
ronag opened this issue Nov 20, 2022 · 14 comments
Open

MakeCallback is very slow #24

ronag opened this issue Nov 20, 2022 · 14 comments
Labels
help wanted Extra attention is needed

Comments

@ronag
Copy link
Member

ronag commented Nov 20, 2022

Calling into JS from CPP is very slow. Is this due to async hooks? Could we disable that with a global option if the user doesn't need async hooks?

@joyeecheung
Copy link
Member

joyeecheung commented Nov 21, 2022

See past discussions nodejs/node#10101 - I think the conclusion was that it wasn't that slow, but that was measured 6 years ago and before V8 rolled out several architectural redesigns. We have also deprecated domains for a long time. Maybe the situations have changed now. Would be good to have some new measurements to see if it is worth a shot making it some kind of optional thing.

@anonrig
Copy link
Member

anonrig commented Nov 21, 2022

Could we disable that with a global option if the user doesn't need async hooks?

Can you explain this in detail? I'm trying to understand the current implementation, and a quick explanation could/would save a lot of time :)

@ronag
Copy link
Member Author

ronag commented Nov 22, 2022

Could we disable that with a global option if the user doesn't need async hooks?

Can you explain this in detail? I'm trying to understand the current implementation, and a quick explanation could/would save a lot of time :)

I don't know much about the details. Async hooks are used to propagate async context. It has a constant overhead even if you don't use it.

@mcollina
Copy link
Member

async_hooks have a tiny overhead if disabled and a significant overhead if enabled.

@ronag
Copy link
Member Author

ronag commented Nov 22, 2022

async_hooks have a tiny overhead if disabled and a significant overhead if enabled.

Is it possible to disable it?

@mcollina
Copy link
Member

Not really. That's described in the linked issue. Maybe a compile-time flag could do it but we are talking ns of difference.

@abenhamdine
Copy link

abenhamdine commented Dec 1, 2022

see also previous related work in
nodejs/node#41331
nodejs/node#41279
some big picture datapoints here, related to uWebSockets module : uNetworking/uWebSockets.js#341

@benjamingr
Copy link
Member

Interesting stuff (though bad manners) at uNetworking/uWebSockets.js#904 namely that experiment (that does break async_hooks but also improves performance)

@H4ad
Copy link
Member

H4ad commented Jul 11, 2023

I could be wrong, but once this nodejs/node#48528 is merged, we can probably rewrite MakeCallback to use AsyncContextFrame, right? Assuming AsyncContextFrame is faster than async_hooks.

@benjamingr
Copy link
Member

@H4ad that would work for people using AsyncLocalStorage but not for people otherwise using async hooks

@Qard
Copy link
Member

Qard commented Oct 2, 2023

I've been wanting to split all the async_hooks stuff in C++ out into a delegate class which we could use a no-op version for until a hook is registered. That would make it easier to at least contain the overhead when it's not in-use.

@joyeecheung
Copy link
Member

joyeecheung commented Oct 19, 2023

My understanding (or, from nodejs/node#10101) is that async hooks do not actually contribute much to the overhead when it's not enabled - and it's opt-in already so users would be willingly taking the hit. Most of the overhead comes from the tick callback for process.nextTick. While it also only incurs a lot of overhead when actually used, due to the prevalence of its usage in Node.js internals and the user land, the overhead is also much more prevalent. Doing something about async hooks helps a bit, but it's also only a very small piece of the puzzle because it's actually not used that much in the user land. process.nextTick() is the real beast here that affect almost all use cases.

@Qard
Copy link
Member

Qard commented Oct 19, 2023

Yes, mostly. Though promises are an order of magnitude larger overhead than tick or timer callbacks. The overhead is non-zero though, so in a hot path which calls MakeCallback often it could make a difference to optimize it with that no-op delegate I mentioned. Not sure if/when I'll get time to do something about that but I'd like to try it at some point. 🤔

@uNetworkingAB
Copy link

The linked-to "ALIEN_UWS" is a valid benchmark of the imposed perf. loss for native addons in Node.js, vs. those written entirely against V8. You can clearly see how significant the loss is, on a whole web server just trying to deliver events to JS.

If you enable(d) ALIEN_UWS (which was removed), vanilla V8 function call was used instead of node::MakeCallback which boosted perf. of the entire web server by 35%. That's not a boost for some simple for-loop doing some addition operation or something like that - that's 35% overall performance boost of an entire HTTP server in full. So the overhead of node::MakeCallback is severe.

For about a decade, this has been known and we do lose about 40% of total perf. only due to node::MakeCallback. If we swap to V8 Function call, we lose about 20% of the native perf.

What I ultimately decided on, was to introduce a short lived HTTP cache in uWS.js so that fewer calls into JS are needed, and this boosted perf. to the top. So we are very much limited by node::MakeCallback and it is a shame that the vanilla V8 performance is not reflected in Node.js.

GSjBzgsWQAEAoOB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

10 participants