Skip to main content

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}