a/sort/shell.rs
1use core::mem::{self, ManuallyDrop, MaybeUninit};
2use core::ptr;
3
4/// Sorts a slice in place using Shell sort.
5///
6/// Shell sort, published by Donald Shell in 1959, is insertion sort with a gap. A pass with
7/// gap `h` insertion sorts every chain of elements `h` apart, `v[c], v[c + h], v[c + 2h], ...`,
8/// so an element far from its place travels there in steps of `h` instead of one slot at a
9/// time. The passes use a decreasing sequence of gaps ending with 1, a plain insertion sort,
10/// which the earlier passes leave with little to do. How fast the sort is depends almost
11/// entirely on the gap sequence: with the ones used here it makes about `1.6–2·n·log₂ n`
12/// comparisons on random input of up to a million elements, though no gap sequence is known
13/// to make Shell sort `O(n log n)`.
14///
15/// Five refinements keep the algorithm as cheap as Shell sort can be:
16///
17/// * **Sorted and reverse-sorted input is recognised.** The slice is first scanned for one
18/// ascending or strictly descending run; if it covers the whole slice, the slice is left
19/// alone or reversed. This costs `n − 1` comparisons on such input and usually two or three
20/// on any other.
21/// * **Hole shifting.** An element is read out once, the greater elements before it on its
22/// chain shift one place along the chain each, and it is written once into the slot left
23/// free: one move per step instead of the three of a swap.
24/// * **The gaps that are fastest, not the fewest comparisons.** Ciura's sequence (`1, 4, 10,
25/// 23, 57, 132, 301, 701`, then times 2.25) makes the fewest comparisons of the sequences
26/// tried, 1.1–1.27 times fewer than Sedgewick's 1982 one (`1, 8, 23, 77, 281, 1073, ...`).
27/// But every pass ends nearly every insertion with a mispredicted branch, and Sedgewick's
28/// gaps grow by a factor of about 4 instead of 2.25, so they take fewer passes: for elements
29/// larger than 8 bytes they are 1.05–1.35 times faster, strings included (except random
30/// strings from a million elements on, 1.1 times slower, where each comparison is a cache
31/// miss). Elements of at most 8 bytes use Ciura's gaps, which suit the window below.
32/// * **A branchless window for small elements.** In a pass over a slice that is still far from
33/// sorted, how far an element moves along its chain is about as hard to predict as a coin
34/// toss: with Ciura's gaps, 0 steps 44% of the time, 1 step 26%, 2 steps 14%, 3 steps 8%
35/// and 4 or more 8%. For elements of at most 8 bytes, each element is compared with the 4
36/// elements before it on its chain at once, how many of them it passes is computed as a
37/// number, and the 5 slots are rewritten from copies chosen by an index computed from that
38/// number: no branch, at the cost of comparisons that turn out unnecessary. Only the 8% of
39/// elements that pass all 4 go on with a branch per step. On random input this is 1.4–1.8
40/// times faster for integers.
41/// * **The window only while it pays.** On a slice that is nearly sorted, insertion's branches
42/// are predictable and the window's extra comparisons and writes are wasted: up to 2.6 times
43/// slower. So a pass takes the window only if the pass before it moved more than a quarter
44/// of the elements; the first pass assumes it did, and the final pass, with gap 1, never
45/// does, since each element there depends on the one just placed.
46///
47/// # Properties
48///
49/// * **Unstable**: equal elements may be reordered (an insertion along a chain can carry an
50/// element past an equal one that lies between the two in the slice).
51/// * **In place**: `O(1)` auxiliary memory, no allocation.
52/// * **Adaptive**: the passes do less on nearly sorted input, and sorted or reverse-sorted
53/// input costs one scan.
54///
55/// | Case | Comparisons | Moves |
56/// |-----------------------------------|--------------------------------|------------------------|
57/// | Best (sorted, reverse, all equal) | `n − 1` | `0` (`n` reversed) |
58/// | Average, Sedgewick's gaps | `≈ (1.6–2)·n·log₂ n` | `O(comparisons)` |
59/// | Average, Ciura's gaps and window | `≈ (2.1–2.8)·n·log₂ n` | `O(comparisons)` |
60/// | Worst | `O(n^(4/3))`, Sedgewick's gaps | the same |
61///
62/// No bound is known for Ciura's gaps, which were found by experiment.
63///
64/// For `n = 1000`: 999 comparisons on sorted or reverse-sorted input and about 15,900 on random
65/// input, or 21,300 with the window, whose unnecessary comparisons are cheap for small
66/// elements; heap sort makes about 10,500.
67///
68/// Against classic Shell sort with Ciura's gaps, plain insertion and the same scan for a run,
69/// this is 1.2–1.75 times faster on random integers, 1.1–1.65 times on nearly sorted ones and
70/// 1.15–1.3 times with few distinct keys; 1.05–1.4 times faster on strings except random ones
71/// from a million elements on, and 1.03–1.3 times on 64-byte elements. Against this crate's
72/// [`comb`](crate::sort::comb) sort, whose fixed compare-exchanges the compiler vectorizes for
73/// integers: 2.4–3.3 times slower on random integers from 1000 elements on, but faster on
74/// nearly sorted input and with few distinct keys up to 1000 elements, and 1.3–2.5 times
75/// faster on strings that are nearly sorted, have few distinct keys or follow a pattern.
76/// Against [`heap`](crate::sort::heap) sort: 1.3–1.7 times slower on random integers from
77/// 1000 elements on, about as fast on random strings, and faster on everything nearly
78/// sorted or patterned.
79///
80/// Shell sort is mainly of educational value; prefer [`slice::sort_unstable`] for real
81/// workloads.
82///
83/// # Panics
84///
85/// Does not panic by itself. If the [`Ord`] implementation of `T` panics, the panic is
86/// propagated and `arr` is left in an unspecified order, but it still holds every original
87/// element exactly once: nothing is dropped twice or leaked. An [`Ord`] that is not a total
88/// order leaves `arr` in an unspecified order with every element exactly once as well.
89///
90/// # Examples
91///
92/// ```
93/// use a::sort::shell;
94///
95/// let mut v = [5, 1, 4, 2, 8, 0, 3, 9, 7, 6];
96/// shell(&mut v);
97/// assert_eq!(v, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
98///
99/// // Small elements far from their place travel in long steps.
100/// let mut v: Vec<u32> = (0..1000).rev().chain(1000..2000).collect();
101/// v.swap(0, 1999);
102/// shell(&mut v);
103/// assert!(v.windows(2).all(|w| w[0] <= w[1]));
104///
105/// let mut words = vec!["pear", "apple", "fig"];
106/// shell(&mut words);
107/// assert_eq!(words, ["apple", "fig", "pear"]);
108/// ```
109pub fn shell<T: Ord>(arr: &mut [T]) {
110 let len = arr.len();
111 if len < 2 {
112 return;
113 }
114 // An ascending run stops at the first descent; a strictly descending one at the first
115 // element that is not smaller, so reversing it is all it takes.
116 let descending = arr[1] < arr[0];
117 let run = 2 + if descending {
118 arr[1..].windows(2).take_while(|w| w[1] < w[0]).count()
119 } else {
120 arr[1..].windows(2).take_while(|w| w[1] >= w[0]).count()
121 };
122 if run == len {
123 if descending {
124 arr.reverse();
125 }
126 return;
127 }
128
129 let mut gaps = [0; MAX_GAPS];
130 if mem::size_of::<T>() <= WINDOW_MAX_SIZE {
131 let count = ciura_gaps(len, &mut gaps);
132 // Whether the last pass moved many elements: the input is still far from sorted, and
133 // the next pass had better not branch on every element.
134 let mut busy = true;
135 for &gap in gaps[..count].iter().rev() {
136 let moved = if busy && gap > 1 {
137 pass_window(arr, gap)
138 } else {
139 pass(arr, gap)
140 };
141 busy = moved > len / BUSY;
142 }
143 } else {
144 let count = sedgewick_gaps(len, &mut gaps);
145 for &gap in gaps[..count].iter().rev() {
146 pass(arr, gap);
147 }
148 }
149}
150
151/// Room for the gaps of any slice: both sequences grow by a factor of at least 2.25.
152const MAX_GAPS: usize = 64;
153
154/// The largest element size, in bytes, for which passes may insert through a window
155/// ([`pass_window`]) and the gaps are Ciura's; larger elements use Sedgewick's gaps and plain
156/// insertion only.
157///
158/// Measured on random input: integers sort 1.4–1.8 times faster with the window, and `u128`
159/// 1.15–1.4 times; but `&str` and `String`, as small as a `u128` and slower to compare, sort
160/// 1.1–1.45 times slower with it on input with few distinct keys or in a sawtooth pattern;
161/// 32-byte elements gain on random input but lose up to 1.75 times elsewhere, and 64-byte
162/// ones lose on all input.
163const WINDOW_MAX_SIZE: usize = 8;
164
165/// How many steps [`pass_window`] takes without branching.
166///
167/// Measured against 3 and 6: 1.05–1.1 times faster than 3 on random input from 100,000
168/// elements on, and up to 1.1 times slower around 1000 elements; 6 is slower on all input.
169const WINDOW: usize = 4;
170
171/// The next pass inserts through a window if this pass moved more than `1 / BUSY` of the
172/// elements, and plainly otherwise.
173///
174/// Measured against 2 and 8: within 3% of 8, and 1.05–1.2 times faster than 2.
175const BUSY: usize = 4;
176
177/// Writes the gaps below `n` of Ciura's sequence, `1, 4, 10, 23, 57, 132, 301, 701`, extended
178/// by multiplying by 2.25 and rounding down, into `gaps` in increasing order, and returns how
179/// many there are. Found by experiment to make about the fewest comparisons of any sequence.
180fn ciura_gaps(n: usize, gaps: &mut [usize; MAX_GAPS]) -> usize {
181 const CIURA: [usize; 8] = [1, 4, 10, 23, 57, 132, 301, 701];
182 let mut count = 0;
183 let mut gap = 1;
184 while gap < n {
185 gaps[count] = gap;
186 count += 1;
187 gap = match CIURA.get(count) {
188 Some(&next) => next,
189 // `⌊2.25·gap⌋`; past `usize::MAX`, the gap is too large for any slice.
190 None => gap.checked_mul(9).map_or(usize::MAX, |g| g / 4),
191 };
192 }
193 count
194}
195
196/// Writes the gaps below `n` of Sedgewick's 1982 sequence, `1` and `4^k + 3·2^(k−1) + 1`
197/// (`8, 23, 77, 281, 1073, ...`), into `gaps` in increasing order, and returns how many there
198/// are. Its gaps grow by a factor of about 4, so it takes fewer passes than Ciura's.
199fn sedgewick_gaps(n: usize, gaps: &mut [usize; MAX_GAPS]) -> usize {
200 let mut count = 0;
201 let mut gap = 1;
202 let mut k = 0u32;
203 while gap < n {
204 gaps[count] = gap;
205 count += 1;
206 k += 1;
207 // Past `usize::MAX`, the gap is too large for any slice.
208 gap = 4usize
209 .checked_pow(k)
210 .and_then(|p| p.checked_add(3 << (k - 1)))
211 .map_or(usize::MAX, |g| g.saturating_add(1));
212 }
213 count
214}
215
216/// Sorts each chain `v[c], v[c + gap], v[c + 2·gap], ...` by insertion: each element is
217/// carried back along its chain past the greater elements before it, which shift one place
218/// down the chain. Returns how many elements moved.
219fn pass<T: Ord>(v: &mut [T], gap: usize) -> usize {
220 let len = v.len();
221 let base = v.as_mut_ptr();
222 let mut moved = 0;
223 for i in gap..len {
224 // SAFETY: `gap <= i < len`, so both elements are inside `v`.
225 if unsafe { *base.add(i) >= *base.add(i - gap) } {
226 continue;
227 }
228 moved += 1;
229 // SAFETY: every offset is `i - m·gap >= 0` for some `m`, inside `v`. The element is
230 // read out, and `hole.dst` is always the one vacant slot: each step shifts the chain
231 // element before it into it. If a comparison panics, dropping `hole` writes the
232 // element into the vacant slot, so `v` holds every element exactly once.
233 unsafe {
234 let x = ManuallyDrop::new(ptr::read(base.add(i)));
235 ptr::copy_nonoverlapping(base.add(i - gap), base.add(i), 1);
236 let mut hole = Hole {
237 src: &*x,
238 dst: base.add(i - gap),
239 };
240 let mut j = i - gap;
241 while j >= gap && *x < *base.add(j - gap) {
242 ptr::copy_nonoverlapping(base.add(j - gap), base.add(j), 1);
243 j -= gap;
244 hole.dst = base.add(j);
245 }
246 // Dropping `hole` writes the element into the vacant slot.
247 }
248 }
249 moved
250}
251
252/// Like [`pass`], but takes the first [`WINDOW`] steps of each insertion without branching.
253/// Returns how many elements moved.
254///
255/// Each element is compared with the `WINDOW` elements before it on its chain, and `k`, the
256/// number of those (counted from the nearest, up to the first that is not greater) it moves
257/// past, is computed as a number. Then each of those `WINDOW` slots is rewritten from a copy
258/// chosen by an index computed from `k`: the slot `m` places back takes the element `m + 1`
259/// places back if `m < k`, the element itself if `m == k`, and its own element otherwise. Only
260/// an element that moves past all `WINDOW` goes on along its chain with a branch per step.
261///
262/// The comparisons beyond the first that fails are made for nothing, but in a pass of a
263/// partly sorted slice how far an element moves is about as hard to predict as a coin toss,
264/// and each misprediction costs more than several comparisons of small elements.
265fn pass_window<T: Ord>(v: &mut [T], gap: usize) -> usize {
266 let len = v.len();
267 let head = WINDOW.saturating_mul(gap).min(len);
268 let mut moved = pass(&mut v[..head], gap);
269 let base = v.as_mut_ptr();
270 for i in head..len {
271 // SAFETY: `i >= WINDOW·gap`, so every `i - m·gap` with `m <= WINDOW` is inside `v`.
272 // The elements of the window are copied out bit for bit into `window` before any is
273 // compared or written: `window[0]` is the element being inserted, `window[m]` the one
274 // `m` places back. A panicking comparison therefore leaves `v` untouched. The writes
275 // that follow cannot panic, and whatever `Ord` answered, `k <= WINDOW` and they place
276 // `window[0..=k]` into the slots `0..=k` places back in a rotated order and leave the
277 // others as they were: a permutation of the window, except when `k == WINDOW`, where
278 // the slot `WINDOW` places back is left vacant for the rest of the insertion, which
279 // is `pass`'s loop with its hole.
280 unsafe {
281 let slot = |m: usize| base.add(i - m * gap);
282 let mut window: [MaybeUninit<T>; WINDOW + 1] =
283 [const { MaybeUninit::uninit() }; WINDOW + 1];
284 for (m, copy) in window.iter_mut().enumerate() {
285 ptr::copy_nonoverlapping(slot(m), copy.as_mut_ptr(), 1);
286 }
287 let x = window[0].as_ptr();
288 let mut k = 0;
289 let mut before = 1;
290 for copy in &window[1..] {
291 before &= usize::from(*x < *copy.as_ptr());
292 k += before;
293 }
294 moved += usize::from(k > 0);
295 for m in 0..WINDOW {
296 let shifted = usize::from(m < k);
297 let placed = usize::from(m == k);
298 let from = shifted * (m + 1) + (1 - shifted - placed) * m;
299 // `from <= m + 1 <= WINDOW`; indexing `window` with a check instead made the
300 // whole sort up to 1.2 times slower.
301 ptr::copy_nonoverlapping(window.as_ptr().add(from).cast::<T>(), slot(m), 1);
302 }
303 if k == WINDOW {
304 let mut hole = Hole {
305 src: x,
306 dst: slot(WINDOW),
307 };
308 let mut j = i - WINDOW * gap;
309 while j >= gap && *x < *base.add(j - gap) {
310 ptr::copy_nonoverlapping(base.add(j - gap), base.add(j), 1);
311 j -= gap;
312 hole.dst = base.add(j);
313 }
314 // Dropping `hole` writes the element into the vacant slot.
315 }
316 }
317 }
318 moved
319}
320
321/// An element held outside a slice and the vacant slot it belongs in: dropping it, when the
322/// insertion is done or a comparison panics, writes the element there.
323struct Hole<T> {
324 src: *const T,
325 dst: *mut T,
326}
327
328impl<T> Drop for Hole<T> {
329 fn drop(&mut self) {
330 // SAFETY: `dst` is the vacant slot of the slice, and `src` the element read out of it.
331 unsafe { ptr::copy_nonoverlapping(self.src, self.dst, 1) }
332 }
333}