Repository navigation
runtime: keep timers in a binary min-heap - #6035
Conversation
|
Based on #6058 , can we remove the calls to |
c796210 to
6d7a0e5
Compare
|
Done. The heap now compares |
|
This will need to be rebased against and get the pico size timings once #6059 is merged. |
|
Please rebase and fix the conflict in binary-size.txt. |
|
Merged dev in and took dev's binary-size.txt. The timer heap no longer changes any size there, and CI is green so far. |
8554dad to
1403343
Compare
|
The latest dev is actually tracking binary sizes for packages with timers now. Last update and then we can merge. 🤞 The code size increase is acceptable for now, and if we need to shrink it on smaller systems later, we can revert to a sorted slice on those teeeny-tiny boards via build tags for the |
A program with thousands of goroutines and many net deadlines or tickers spent about a quarter of its CPU in removeTimer, with more in timerQueueAdd and whenTicks. The timer queue was a sorted linked list. Adding a timer walked the list to find its place, and stopping or resetting one walked it to find the node, so every operation was O(n) in the number of pending timers. Keep the queue in a slice backed min-heap ordered by deadline and then by an insertion sequence number, so equal deadlines still fire in FIFO order. Each timer points to its queued node and each node knows its heap index, which makes stop and reset O(log n). The synctest bubble uses the same heap, and its alternating placement of equal deadlines is kept by giving front insertions a negative sequence number. The global heap is allocated by the first timer, so programs without timers still drop the timer code from the scheduler.
Replace up and down with one fix(i, tn), hold the node in a local and write it once, and inline the comparison and index update, which asyncify otherwise instruments. Adds heap tests: pop order, equal deadline FIFO, remove, and randomized operations against a reference model.
The heap kept its own copy of the deadline in timerNode.when. A timer's deadline only changes while none of its nodes is queued, so the heap can compare timer.when directly, like the list does after tinygo-org#6058. This drops the copy and makes whenTicks the same as on dev again. Equal deadlines still keep insertion order through seq.
1403343 to
2d78faf
Compare
|
CI stalling on https://github.com/tinygo-org/tinygo/actions/runs/41683974625/job/126108167383#step:14:15 .. I cancelled and restarted and it hung again. I'll restart it later today and hopefully we can get this merged. Thanks for all your work on these PRs. Hopefully it won't take too long to work through the back log since I think most of them have been reviewed and approved in their draft state. |
|
I don't think it's hung. The ASSERT=1 test step prints nothing until it finishes, and on dev today assert-test-linux took 35 to 41 minutes (runs 41687213221 and 41679512096). The run you cancelled had been in that step for about 11 minutes. Before the rebase, the same job passed on this branch in 27 minutes. If it is still silent past about 45 minutes, then it's a real hang and I'll dig in. |
|
Ok, I'll restart it and see what happens. Sorry for being too impatient then :D |
Fixes #5956.
The timer queue was a sorted list, so adding or removing a timer cost O(n), which shows up with many timers. It is now a binary min-heap. Ties keep FIFO order by sequence number, and a program without timers still compiles no heap code. The second commit is @dgryski's from the issue. It folds sift-up and sift-down into one function to make the heap smaller, and adds heap tests that check pop order, ties, removal, and randomized operations against a reference model.
From the size tables in the issue (-opt=z): no change for programs without timers or with one Sleep. With 100 timers reset and stopped, +1913 bytes of wasip1 code, +274 bytes on linux/amd64, +496 bytes on pico.
TestTimerHeap* passes on linux/amd64 and wasip1, and so does TestBuild/Host/timers.go.