-
Notifications
You must be signed in to change notification settings - Fork 131
perf: optimize event_loop_tick and async ops #1158
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
Conversation
Timer in deno is way slower than Node.js and Bun. (the result is better in dcore with that dcore ported version above tho)
I added traces for time consumed for queue timers, poll timers, and call timers, ![]() (https://canary.discord.com/channels/684898665143206084/688040863313428503/1393599894022783038) If I just took the worst case, and multiplied by the timer count, and it's a value about the same to the actual result. It needs two inserts to the BTree, and it's probably the reason. Take a look at the implementation of Node.js ![]() Take a look at Bun's Timer implementation, it's using a Pairing Heap [1] The time complexity is ![]() While for BTree (that deno_core uses), it's https://en.wikipedia.org/wiki/Pairing_heap#Summary_of_running_times |
core/rebuild_async_stubs.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, lots of interesting stuff in here. I'd like to ask you to split this PR into smaller chunks though - it's quite hard to review (and merge) with confidence with so many moving pieces. I think it should be split into 3 distinct PRs in following order:
- changes to handling of promises in ops
- changes to
op2
macro (once I understand why they are needed anyway) - changes to timers implementation
Let me know if you need any help with that
pub(crate) js_event_loop_tick_cb: RefCell<Option<v8::Global<v8::Function>>>, | ||
pub(crate) timers: WebTimers<v8::Global<v8::Function>>, | ||
pub(crate) js_wasm_streaming_cb: RefCell<Option<v8::Global<v8::Function>>>, | ||
pub(crate) js_bindings: RefCell<Option<JsBindings>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you actually store these callbacks/bindings as separate functions instead of additional struct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought put them together will be a good option, or I have to wrap them in RefCell<Option<...>>
individually, i don't think it's necessary to have many these wrappers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this collects all callbacks into the struct, leading to bifurcation. Let's keep them separate and once these changes are done we can refactor all callback handling into a single struct.
let isolate = unsafe { raw_ptr.as_mut().unwrap() }; | ||
// These globals will prevent snapshots from completing, take them | ||
state.exception_state.prepare_to_destroy(); | ||
std::mem::take(&mut *state.js_event_loop_tick_cb.borrow_mut()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be kept - if you don't drop these globals before snapshotting, it will most likely error out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I forgotten
generator_state: &mut GeneratorState, | ||
ret_type: &Arg, | ||
) -> Result<TokenStream, V8MappingError> { | ||
// In the future we may be able to make this false for void again |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain why changes to the op2
macro are needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TL;DR Since I can just resolve
and reject
natively, I changed the macros to resolve
reject
the promise instead of scope.throw
or retval.set
. Avoid the cost of throw
ing errors.
Oringinally, dispatch async uses the same code as dispatch slow, if it's Err
immediately, it throw errors directly to scope, while in async, the errors should result in Promise.reject
.
So In the async wrap, it does try { ... } catch (err) { return Promise.reject(err) }
.
Now I removed the async wrap, the error will be thrown directly, that might cause problem.
Since I can just resolve
and reject
natively, I changed the macros to resolve
reject
the promise instead of scope.throw
or retval.set
These two needs to be done together tho:
since I needs needs resolve promise natively, currently the op2 macro needs the async wrap to behave correctly. This is still WIP, I guess I would use a Pairing Heap or something and check if there's performance improvements:
|
Yeah, that's fine. 2 PRs then 👍 |
Superseded by
Promise
s natively for async ops #1163Summary
34% more performant async ops + less memory usage
(by resolve
Promise
s natively)