-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
timers: fix the order of timers under a long loop #46644
base: main
Are you sure you want to change the base?
Conversation
This is supposed to fix this issue right? #37226 (comment)
|
It seems to be. |
ea62bbf
to
cb2f09d
Compare
For my macbook M1, I ran several times. The original logic of benchmark/timers/timers-timeout-pooled.js is about:
The logic of current PR is about:
The unpooled one is about:
|
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 believe this change addresses the issue holistically because there's nothing stopping any n-th list from having a timer that is supposed to expire next. This was an intentional sacrifice made back when the ring of timers based on duration idea was conceived.
https://github.com/nodejs/node/blob/main/lib/internal/timers.js#LL37C5-L37C5
That's what I thought at first, but If I understand the code correctly, the heap reorders itself after every specific list, so it shouldn't have the nth list issue because of line 531? For example, in the test you go to list 10 and then list 15 and then list 10 again, but in theory you could go anywhere. You could, however, zigzag between all kinds of lists - but I think that it's an edge case. |
Yes, but secondary function would need to compare every single list in order to find the one with the next-most expiry. Edit: Ehh, maybe not. Let me think about this more. Edit2: Yes, initial assumption was right. Because once you check the next list, what's stopping the next expiry from being further down? So your pool of possible lists to check keeps growing. |
I'm not sure I'm following. The next expiry must either be the next one in the current list, or the next minimum expiry in the heap (which is heap[2] or heap[3]). (as a wave-hand proof: If I understand how the heap is working, if the "correct" next element were in a different list then that list would actually have a lower expiry than heap[2] and heap[3] - and it wouldn't be deeper than them in the heap). Do you mean that the extra cost is the fact that we have to fix the heap after every list run (interpolate) - which can be very costly if we have to zig/zag.
If I understand correctly, Line 531 is what's supposed to ensure that. |
How about after that? :) |
If I understand the code correctly, Line 531 is what's supposed to ensure that. |
Yes, which is terribly inefficient. The proposal here is resorting binary heap after every timer. Then we might as well not use a binary heap. The whole purpose here is to ensure that the most common use-cases of Node.js are performance efficient. Which means that even if you run into the scenario where you have a high number of lists and high number of expired timers in each, you're not having to sort the full list but rather have more granular increment at which sorting & insertion happens. |
I was just advocating for the correctness of the code, not the complexity. Note that that specific line was already there and is already happening, but as you say it might happen much more now (as previously a list ran at most once, and you were limited by the amount of lists, and now a specific list can run many more times). |
The original code will update the order after each inserting, removing, or changing value. So just only to compare heap 2 or 3. And the only complexity added is comparing with the secondary one. No more percolateDown or percolateUp. Edit: Each switching between 2 lists will do a percolateDown, which I think it's a unusual situation (also is the situation I try to solve in this PR). Edit2: Not every tick will last long to expire several timers in a single list. |
cb2f09d
to
7f63aa2
Compare
/ping @apapirovski |
I think the code is fine. But it may need to adjust the binary heap constantly to make sure the timers execute in the correct order. But it seems more important to the user to execute the timer in order ? |
I think that a better benchmark should be thought of that shows how much this impacts the timers, in order to understand how much this affects performance. |
Percolate is a worst-case O(log N) operation. You're potentially performing it after every timer in the list. There's simply no way this PR can land given the performance implications for the worst-case scenario. Node.js already clearly specifies that timer order is NOT guaranteed. Timer order isn't even guaranteed in linux which can have this exact scenario playout given how the timer wheel works. |
Fixes: #37226 (comment)