Skip to content
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

Improve sift_down performance in BinaryHeap #81127

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
Collaborator

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
Contributor

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
Contributor

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in 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