core/slice/sort/unstable/mod.rs
1//! This module contains the entry points for `slice::sort_unstable`.
2
3use crate::mem::SizedTypeProperties;
4use crate::ops::{Range, RangeBounds};
5use crate::slice::sort::select::partition_at_index;
6#[cfg(not(any(feature = "optimize_for_size", target_pointer_width = "16")))]
7use crate::slice::sort::shared::find_existing_run;
8#[cfg(not(any(feature = "optimize_for_size", target_pointer_width = "16")))]
9use crate::slice::sort::shared::smallsort::insertion_sort_shift_left;
10use crate::{cfg_select, intrinsics, slice};
11
12pub(crate) mod heapsort;
13pub(crate) mod quicksort;
14
15/// Unstable sort called ipnsort by Lukas Bergdoll and Orson Peters.
16/// Design document:
17/// <https://github.com/Voultapher/sort-research-rs/blob/main/writeup/ipnsort_introduction/text.md>
18///
19/// Upholds all safety properties outlined here:
20/// <https://github.com/Voultapher/sort-research-rs/blob/main/writeup/sort_safety/text.md>
21#[inline(always)]
22pub fn sort<T, F>(v: &mut [T], is_less: &mut F)
23where
24 F: FnMut(&T, &T) -> bool,
25{
26 // Arrays of zero-sized types are always all-equal, and thus sorted.
27 if T::IS_ZST {
28 return;
29 }
30
31 // Instrumenting the standard library showed that 90+% of the calls to sort
32 // by rustc are either of size 0 or 1.
33 let len = v.len();
34 if intrinsics::likely(len < 2) {
35 return;
36 }
37
38 cfg_select! {
39 any(feature = "optimize_for_size", target_pointer_width = "16") => {
40 heapsort::heapsort(v, is_less);
41 }
42 _ => {
43 // More advanced sorting methods than insertion sort are faster if called in
44 // a hot loop for small inputs, but for general-purpose code the small
45 // binary size of insertion sort is more important. The instruction cache in
46 // modern processors is very valuable, and for a single sort call in general
47 // purpose code any gains from an advanced method are cancelled by i-cache
48 // misses during the sort, and thrashing the i-cache for surrounding code.
49 const MAX_LEN_ALWAYS_INSERTION_SORT: usize = 20;
50 if intrinsics::likely(len <= MAX_LEN_ALWAYS_INSERTION_SORT) {
51 insertion_sort_shift_left(v, 1, is_less);
52 return;
53 }
54
55 ipnsort(v, is_less);
56 }
57 }
58}
59
60/// Unstable partial sort the range `start..end`, after which it's guaranteed that:
61///
62/// 1. Every element in `v[..start]` is smaller than or equal to
63/// 2. Every element in `v[start..end]`, which is sorted, and smaller than or equal to
64/// 3. Every element in `v[end..]`.
65#[inline]
66pub fn partial_sort<T, F, R>(v: &mut [T], range: R, mut is_less: F)
67where
68 F: FnMut(&T, &T) -> bool,
69 R: RangeBounds<usize>,
70{
71 // Arrays of zero-sized types are always all-equal, and thus sorted.
72 if T::IS_ZST {
73 return;
74 }
75
76 let len = v.len();
77 let Range { start, end } = slice::range(range, ..len);
78
79 if end - start <= 1 {
80 // Empty range or single element. This case can be resolved in at most
81 // single partition_at_index call, without further sorting.
82
83 if end == 0 || start == len {
84 // Do nothing if it is an empty range at start or end: all guarantees
85 // are already upheld.
86 return;
87 }
88
89 partition_at_index(v, start, &mut is_less);
90 return;
91 }
92
93 // A heuristic factor to decide whether to partition the slice or not.
94 // If the range bound is close to the edges of the slice, it's not worth
95 // partitioning first.
96 const PARTITION_THRESHOLD: usize = 8;
97 let mut v = v;
98 if end + PARTITION_THRESHOLD <= len {
99 v = partition_at_index(v, end - 1, &mut is_less).0;
100 }
101 if start >= PARTITION_THRESHOLD {
102 v = partition_at_index(v, start, &mut is_less).2;
103 }
104
105 sort(v, &mut is_less);
106}
107
108/// See [`sort`]
109///
110/// Deliberately don't inline the main sorting routine entrypoint to ensure the
111/// inlined insertion sort i-cache footprint remains minimal.
112#[cfg(not(any(feature = "optimize_for_size", target_pointer_width = "16")))]
113#[inline(never)]
114fn ipnsort<T, F>(v: &mut [T], is_less: &mut F)
115where
116 F: FnMut(&T, &T) -> bool,
117{
118 let len = v.len();
119 let (run_len, was_reversed) = find_existing_run(v, is_less);
120
121 // SAFETY: find_existing_run promises to return a valid run_len.
122 unsafe { intrinsics::assume(run_len <= len) };
123
124 if run_len == len {
125 if was_reversed {
126 v.reverse();
127 }
128
129 // It would be possible to a do in-place merging here for a long existing streak. But that
130 // makes the implementation a lot bigger, users can use `slice::sort` for that use-case.
131 return;
132 }
133
134 // Limit the number of imbalanced partitions to `2 * floor(log2(len))`.
135 // The binary OR by one is used to eliminate the zero-check in the logarithm.
136 let limit = 2 * (len | 1).ilog2();
137 crate::slice::sort::unstable::quicksort::quicksort(v, None, limit, is_less);
138}