-
Notifications
You must be signed in to change notification settings - Fork 12.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
BinaryHeap: Simplify sift down #29811
Conversation
(rust_highfive has picked a reviewer for you, use r? to override) |
302c292
to
cee674f
Compare
I don't quite understand why sift down was so complicated to start with. It's certainly not needed for heapify, push or pop, but something else? |
4393a5c
to
75c0972
Compare
unsafe { | ||
let mut hole = Hole::new(&mut self.data, pos); | ||
let mut child = 2 * pos + 1; | ||
while child < end { | ||
let right = child + 1; | ||
// compare with the greater the two children |
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.
of the two
I'm guessing this was an artifact of refactoring that wasn't caught in prior review. I'm nervous about this just because I don't understand what the old code was trying to do. |
@@ -521,29 +521,30 @@ impl<T: Ord> BinaryHeap<T> { | |||
|
|||
while hole.pos() > start { | |||
let parent = (hole.pos() - 1) / 2; | |||
if hole.removed() <= hole.get(parent) { break } | |||
if hole.element() <= hole.get(parent) { break } |
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.
iirc the style is to add the semi after the break?
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.
will do
I double checked the defn of heap sort, and this seems to check out. r=me with nits |
Sift down was doing all too much work: it can stop directly when the current element obeys the heap property in relation to its children. In the old code, sift down didn't compare the element to sift down at all, so it was maximally sifted down and relied on the sift up call to put it in the correct location. This should speed up heapify and .pop(). Also rename Hole::removed() to Hole::element()
75c0972
to
81fcdd4
Compare
Applied the fixes, and improved the commit log text to point out why the old code worked. Thank you for the review! |
@bors r=gankro |
📌 Commit 81fcdd4 has been approved by |
BinaryHeap: Simplify sift down Sift down was doing all too much work: it can stop directly when the current element obeys the heap property in relation to its children. In the old code, sift down didn't compare the element to sift down at all, so it was maximally sifted down and relied on the sift up call to put it in the correct location. This should speed up heapify and .pop(). Also rename Hole::removed() to Hole::element()
Here's a microbenchmark Comparing rust versions: It suggest a slight improvment with this PR. It's a minimum spanning tree computation on a graph of 450 edges. The algorithm spends most of its time popping off the least edge from a BinaryHeap, and its runtime improves slightly:
|
From the Python source code, which also maximally sifts down and then back up:
I think this change could use some more benchmarks; I suspect whether you win or lose performance depends on how expensive the comparison function is compared to moving elements around in memory. |
@dgrunwald In Python, list element swaps are very cheap (one PyObject *), and comparisons expensive (python function call). |
Indeed, in the same kind of benchmark where the edge weight is a type with more expensive comparison function ( Maybe we can use separate sift_down methods for pop and for heapify (BinaryHeap::from). |
BinaryHeap: Use full sift down in .pop() .sift_down can either choose to compare the element on the way down (and place it during descent), or to sift down an element fully, then sift back up to place it. A previous PR changed .sift_down() to the former behavior, which is much faster for relatively small heaps and for elements that are cheap to compare. A benchmarking run suggested that BinaryHeap::pop() suffers improportionally from this, and that it should use the second strategy instead. It's logical since .pop() brings last element from the heapified vector into index 0, it's very likely that this element will end up at the bottom again. Closes #29969 Previous PR #29811
BinaryHeap: Simplify sift down
Sift down was doing all too much work: it can stop directly when the
current element obeys the heap property in relation to its children.
In the old code, sift down didn't compare the element to sift down at
all, so it was maximally sifted down and relied on the sift up call to
put it in the correct location.
This should speed up heapify and .pop().
Also rename Hole::removed() to Hole::element()