Skip to content

Improve sift_down performance in BinaryHeap #81127

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 1 commit into from
Mar 9, 2021

Conversation

hanmertens
Copy link
Contributor

Replacing child < end - 1 with child <= end.saturating_sub(2) in BinaryHeap::sift_down_range (surprisingly) results in a significant speedup of BinaryHeap::into_sorted_vec. The same substitution can be done for BinaryHeap::sift_down_to_bottom, which causes a slight but probably statistically insignificant speedup for BinaryHeap::pop. It's interesting that benchmarks aside from bench_into_sorted_vec are barely affected, even those that do use sift_down_* methods internally.

Benchmark Before (ns/iter) After (ns/iter) Speedup
bench_find_smallest_10001 392,617 385,200 1.02
bench_from_vec1 506,016 504,444 1.00
bench_into_sorted_vec1 476,869 384,458 1.24
bench_peek_mut_deref_mut3 518,753 519,792 1.00
bench_pop2 446,718 444,409 1.01
bench_push3 772,481 770,208 1.00

1: internally calls sift_down_range
2: internally calls sift_down_to_bottom
3: should not be affected

@rust-highfive
Copy link
Contributor

r? @shepmaster

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 17, 2021
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Feb 7, 2021
JohnTitor added a commit to JohnTitor/rust that referenced this pull request Feb 21, 2021
…e, r=Mark-Simulacrum

Document BinaryHeap unsafe functions

`BinaryHeap` contains some private safe functions but that are actually unsafe to call. This PR marks them `unsafe` and documents all the `unsafe` function calls inside them.

While doing this I might also have found a bug: some "SAFETY" comments in `sift_down_range` and `sift_down_to_bottom` are valid only if you assume that `child` doesn't overflow. However it may overflow if `end > isize::MAX` which can be true for ZSTs (but I think only for them). I guess the easiest fix would be to skip any sifting if `mem::size_of::<T> == 0`.

Probably conflicts with rust-lang#81127 but solving the eventual merge conflict should be pretty easy.
JohnTitor added a commit to JohnTitor/rust that referenced this pull request Feb 21, 2021
…e, r=Mark-Simulacrum

Document BinaryHeap unsafe functions

`BinaryHeap` contains some private safe functions but that are actually unsafe to call. This PR marks them `unsafe` and documents all the `unsafe` function calls inside them.

While doing this I might also have found a bug: some "SAFETY" comments in `sift_down_range` and `sift_down_to_bottom` are valid only if you assume that `child` doesn't overflow. However it may overflow if `end > isize::MAX` which can be true for ZSTs (but I think only for them). I guess the easiest fix would be to skip any sifting if `mem::size_of::<T> == 0`.

Probably conflicts with rust-lang#81127 but solving the eventual merge conflict should be pretty easy.
JohnTitor added a commit to JohnTitor/rust that referenced this pull request Feb 21, 2021
…e, r=Mark-Simulacrum

Document BinaryHeap unsafe functions

`BinaryHeap` contains some private safe functions but that are actually unsafe to call. This PR marks them `unsafe` and documents all the `unsafe` function calls inside them.

While doing this I might also have found a bug: some "SAFETY" comments in `sift_down_range` and `sift_down_to_bottom` are valid only if you assume that `child` doesn't overflow. However it may overflow if `end > isize::MAX` which can be true for ZSTs (but I think only for them). I guess the easiest fix would be to skip any sifting if `mem::size_of::<T> == 0`.

Probably conflicts with rust-lang#81127 but solving the eventual merge conflict should be pretty easy.
@bors
Copy link
Collaborator

bors commented Feb 21, 2021

☔ The latest upstream changes (presumably #82359) made this pull request unmergeable. Please resolve the merge conflicts.

Because child > 0, the two statements are equivalent, but using
saturating_sub and <= yields in faster code. This is most notable in the
binary_heap::bench_into_sorted_vec benchmark, which shows a speedup of
1.26x, which uses sift_down_range internally. The speedup of pop (that
uses sift_down_to_bottom internally) is much less significant as the
sifting method is not called in a loop.
@hanmertens hanmertens force-pushed the binary_heap_sift_down_perf branch from bdf2962 to 095bf01 Compare February 21, 2021 15:43
@shepmaster
Copy link
Member

r? @dtolnay

@rust-highfive rust-highfive assigned dtolnay and unassigned shepmaster Mar 8, 2021
Copy link
Member

@dtolnay dtolnay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @hanmertens. I am not familiar with the BinaryHeap implementation but I am prepared to accept this on the basis of the into_sorted_vec benchmark. I confirmed that the behavior is logically identical to before in both places.

  • If end >= 2 then child < end - 1 is equivalent to child <= end - 2 is equivalent to child <= end.saturating_sub(2).

  • If end == 1 then child < end - 1 is false while child <= end.saturating_sub(2) is equivalent to child == 0. However it's a loop invariant that child == 2 * hole.pos() + 1 > 0 so child != 0 and child <= end.saturating_sub(2) is also false.

  • If end == 0 then contradiction because it's a precondition of the function that 0 <= pos < end, and end is not mutated.

@dtolnay
Copy link
Member

dtolnay commented Mar 9, 2021

@bors r+

@bors
Copy link
Collaborator

bors commented Mar 9, 2021

📌 Commit 095bf01 has been approved by dtolnay

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 9, 2021
m-ou-se added a commit to m-ou-se/rust that referenced this pull request Mar 9, 2021
…rf, r=dtolnay

Improve sift_down performance in BinaryHeap

Replacing `child < end - 1` with `child <= end.saturating_sub(2)` in `BinaryHeap::sift_down_range` (surprisingly) results in a significant speedup of `BinaryHeap::into_sorted_vec`. The same substitution can be done for `BinaryHeap::sift_down_to_bottom`, which causes a slight but probably statistically insignificant speedup for `BinaryHeap::pop`. It's interesting that benchmarks aside from `bench_into_sorted_vec` are barely affected, even those that do use `sift_down_*` methods internally.

| Benchmark                | Before (ns/iter) | After (ns/iter) | Speedup |
|--------------------------|------------------|-----------------|---------|
| bench_find_smallest_1000<sup>1</sup> | 392,617          | 385,200         |    1.02 |
| bench_from_vec<sup>1</sup>           | 506,016          | 504,444         |    1.00 |
| bench_into_sorted_vec<sup>1</sup>    | 476,869          | 384,458         |    1.24 |
| bench_peek_mut_deref_mut<sup>3</sup> | 518,753          | 519,792         |    1.00 |
| bench_pop<sup>2</sup>                | 446,718          | 444,409         |    1.01 |
| bench_push<sup>3</sup>               | 772,481          | 770,208         |    1.00 |

<sup>1</sup>: internally calls `sift_down_range`
<sup>2</sup>: internally calls `sift_down_to_bottom`
<sup>3</sup>: should not be affected
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 9, 2021
Rollup of 8 pull requests

Successful merges:

 - rust-lang#81127 (Improve sift_down performance in BinaryHeap)
 - rust-lang#81879 (Added #[repr(transparent)] to core::cmp::Reverse)
 - rust-lang#82048 (or-patterns: disallow in `let` bindings)
 - rust-lang#82731 (Bump libc dependency of std to 0.2.88.)
 - rust-lang#82799 (Add regression test for rust-lang#75525)
 - rust-lang#82841 (Change x64 size checks to not apply to x32.)
 - rust-lang#82883 (Update Cargo)
 - rust-lang#82887 (Update CONTRIBUTING.md)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit c013dc0 into rust-lang:master Mar 9, 2021
@rustbot rustbot added this to the 1.52.0 milestone Mar 9, 2021
@hanmertens hanmertens deleted the binary_heap_sift_down_perf branch March 9, 2021 12:52
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants