-
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
Add Median of Medians fallback to introselect #107522
Conversation
r? @cuviper (rustbot has picked a reviewer for you, use r? to override) |
Hey! It looks like you've submitted a new PR for the library teams! If this PR contains changes to any Examples of
|
Forgot to mention, on random input the fast deterministic select is a bit slower than the pdq based quickselect, so I didn't make |
This comment was marked as outdated.
This comment was marked as outdated.
Ahh, my apologies, I read Putting it back, |
I tried also implementing the |
☔ The latest upstream changes (presumably #107191) made this pull request unmergeable. Please resolve the merge conflicts. |
089165c
to
9dfe219
Compare
Hmm, now that #106933 was merged, the API guarantees have been loosened to O(n log n) worst case runtime. I'm guessing we'd need an ACP to get it back down to O(n) worst case (and it's unclear to me if we'd even want to guarantee O(n)). Should I add note in the |
So first and foremost, the PR in its current form does not make the behavior linear, because the Secondly, I think having the full Fast Deterministic Selection algorithm as a fallback algorithm does not make much sense. Either it should be run from start to finish, or we should reduce it to just the |
library/core/src/slice/select.rs
Outdated
|
||
/// helper function used to find the index of the min/max element | ||
/// using e.g. `slice.iter().enumerate().min_by(from_is_less(&mut is_less)).unwrap()` | ||
fn from_is_less<T>( |
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.
It seems like the code could be made more readable by just having min_index
and max_index
helper functions that return an Option<usize>
.
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.
It seems like the code could be made more readable by just having
min_index
andmax_index
helper functions that return anOption<usize>
.
I don't think this has been addressed yet.
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.
Yeah I kinda forgot about it, should be fixed now though.
I had naively thought that the same limiting strategy used for introsort would also work for introselect, but yeah makes sense that it doesn't. Does this mean that to guarantee linear runtime we need a constant limit on the iterations?
In my tests, just using fast deterministic selection unconditionally was a bit slower than the current median-of-3 quickselect on random inputs, so I didn't want to do that. I also tried to just use median of medians first, but that was slower than using heapsort as a fallback. I haven't tried just using median of ninthers yet, I'll have to see if that still gets similar speedups to the full fast deterministic select. |
I suppose it could do something like |
I think you should do what I did in pdqsort originally: track whether the partition was 'good' or 'bad' by some threshold (I used n / 8 = 12.5% quantile for the pivot position as the cutoff), and then and only then increment the counter. This way you can put the counter relatively low without affecting performance for normal code while quickly switching to the fallback for malicious/unlucky inputs. |
Performance on perfectly random inputs is slightly overrated. What about nonrandom inputs, like tokenized data from Wikipedia or such? |
I currently don't have access to a powerful machine, but I can try to set up a somewhat comprehensive benchmark suite once I do to compare heapsort fallback vs median-of-ninthers fallback vs just fast deterministic select. |
Sorry, I haven't had time to dig into this - let me roll a new reviewer... r? libs |
I still need to change some stuff about the PR so @rustbot author |
@Sp00ph any updates on this? |
Not yet, I'll see if I can get around to it some time soon. |
9dfe219
to
3d11b65
Compare
I wrote a little benchmark suite testing the current selection implementation, quickselect with median of medians as fallback and pure fast deterministic select on various slice lengths and input shapes and quickselect with median of medians fallback was consistently the fastest, so I changed it to that now. If anyone is interested I can upload the benchmark itself too. The algorithm should now also actually be O(n) worst case since I made the quickselect iteration limit constant now. |
@rustbot ready |
@bors r+ |
I forgot to ask: As of #106933, |
This can be submitted as a separate PR which we will run an FCP on, since this is a new stable guarantee. No ACP is required. |
…mpiler-errors Rollup of 7 pull requests Successful merges: - rust-lang#107522 (Add Median of Medians fallback to introselect) - rust-lang#111152 (update `pulldown-cmark` to `0.9.3`) - rust-lang#111757 (Consider lint check attributes on match arms) - rust-lang#111831 (Always capture slice when pattern requires checking the length) - rust-lang#111929 (Don't print newlines in APITs) - rust-lang#111945 (Migrate GUI colors test to original CSS color format) - rust-lang#111950 (Remove ExpnKind::Inlined.) r? `@ghost` `@rustbot` modify labels: rollup
Update runtime guarantee for `select_nth_unstable` rust-lang#106933 changed the runtime guarantee for `select_nth_unstable` from O(n) to O(n log n), since the old guarantee wasn't actually met by the implementation at the time. Now with rust-lang#107522, `select_nth_unstable` should be truly linear in runtime, so we can revert its runtime guarantee to O(n). Since rust-lang#106933 was considered a bug fix, this will probably need an FCP because it counts as a new API guarantee. r? `@Amanieu`
Fixes #102451.
This PR is a follow up to #106997. It adds a Fast Deterministic Selection implementation as a fallback to the introselect algorithm used by
select_nth_unstable
. This allows it to guarantee O(n) worst case running time, while maintaining good performance in all cases.This would fix #102451, which was opened because the
select_nth_unstable
docs falsely claimed that it had O(n) worst case performance, even though it was actually quadratic in the worst case. #106997 improved the worst case complexity to O(n log n) by using heapsort as a fallback, and this PR further improves it to O(n) (this would also make #106933 unnecessary).It also improves the actual runtime if the fallback gets called: Using a pathological input of size
1 << 19
(see the playground link in #102451), calculating the median is roughly 3x faster using fast deterministic selection as a fallback than it is using heapsort.The downside to this is less code reuse between the sorting and selection algorithms, but I don't think it's that bad. The additional algorithms are ~250 LOC with no
unsafe
blocks (I tried using unsafe to avoid bounds checks but it didn't noticeably improve the performance).I also let it fuzz for a while against the current
select_nth_unstable
implementation to ensure correctness, and it seems to still fulfill all the necessary postconditions.cc @scottmcm who reviewed #106997