Skip to content

Commit f1913e2

Browse files
author
Stjepan Glavina
committed
Implement feature sort_unstable
1 parent 58c701f commit f1913e2

File tree

10 files changed

+967
-113
lines changed

10 files changed

+967
-113
lines changed

src/libcollections/benches/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![deny(warnings)]
1212

1313
#![feature(rand)]
14+
#![feature(sort_unstable)]
1415
#![feature(test)]
1516

1617
extern crate test;

src/libcollections/benches/slice.rs

Lines changed: 61 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ fn random_inserts(b: &mut Bencher) {
169169
}
170170
})
171171
}
172+
172173
#[bench]
173174
fn random_removes(b: &mut Bencher) {
174175
let mut rng = thread_rng();
@@ -216,65 +217,76 @@ fn gen_mostly_descending(len: usize) -> Vec<u64> {
216217
v
217218
}
218219

219-
fn gen_big_random(len: usize) -> Vec<[u64; 16]> {
220+
fn gen_strings(len: usize) -> Vec<String> {
220221
let mut rng = thread_rng();
221-
rng.gen_iter().map(|x| [x; 16]).take(len).collect()
222-
}
223-
224-
fn gen_big_ascending(len: usize) -> Vec<[u64; 16]> {
225-
(0..len as u64).map(|x| [x; 16]).take(len).collect()
222+
let mut v = vec![];
223+
for _ in 0..len {
224+
let n = rng.gen::<usize>() % 20 + 1;
225+
v.push(rng.gen_ascii_chars().take(n).collect());
226+
}
227+
v
226228
}
227229

228-
fn gen_big_descending(len: usize) -> Vec<[u64; 16]> {
229-
(0..len as u64).rev().map(|x| [x; 16]).take(len).collect()
230+
fn gen_big_random(len: usize) -> Vec<[u64; 16]> {
231+
let mut rng = thread_rng();
232+
rng.gen_iter().map(|x| [x; 16]).take(len).collect()
230233
}
231234

232-
macro_rules! sort_bench {
233-
($name:ident, $gen:expr, $len:expr) => {
235+
macro_rules! sort {
236+
($f:ident, $name:ident, $gen:expr, $len:expr) => {
234237
#[bench]
235238
fn $name(b: &mut Bencher) {
236-
b.iter(|| $gen($len).sort());
239+
b.iter(|| $gen($len).$f());
237240
b.bytes = $len * mem::size_of_val(&$gen(1)[0]) as u64;
238241
}
239242
}
240243
}
241244

242-
sort_bench!(sort_small_random, gen_random, 10);
243-
sort_bench!(sort_small_ascending, gen_ascending, 10);
244-
sort_bench!(sort_small_descending, gen_descending, 10);
245-
246-
sort_bench!(sort_small_big_random, gen_big_random, 10);
247-
sort_bench!(sort_small_big_ascending, gen_big_ascending, 10);
248-
sort_bench!(sort_small_big_descending, gen_big_descending, 10);
249-
250-
sort_bench!(sort_medium_random, gen_random, 100);
251-
sort_bench!(sort_medium_ascending, gen_ascending, 100);
252-
sort_bench!(sort_medium_descending, gen_descending, 100);
253-
254-
sort_bench!(sort_large_random, gen_random, 10000);
255-
sort_bench!(sort_large_ascending, gen_ascending, 10000);
256-
sort_bench!(sort_large_descending, gen_descending, 10000);
257-
sort_bench!(sort_large_mostly_ascending, gen_mostly_ascending, 10000);
258-
sort_bench!(sort_large_mostly_descending, gen_mostly_descending, 10000);
259-
260-
sort_bench!(sort_large_big_random, gen_big_random, 10000);
261-
sort_bench!(sort_large_big_ascending, gen_big_ascending, 10000);
262-
sort_bench!(sort_large_big_descending, gen_big_descending, 10000);
245+
macro_rules! sort_expensive {
246+
($f:ident, $name:ident, $gen:expr, $len:expr) => {
247+
#[bench]
248+
fn $name(b: &mut Bencher) {
249+
b.iter(|| {
250+
let mut v = $gen($len);
251+
let mut count = 0;
252+
v.$f(|a: &u64, b: &u64| {
253+
count += 1;
254+
if count % 1_000_000_000 == 0 {
255+
panic!("should not happen");
256+
}
257+
(*a as f64).cos().partial_cmp(&(*b as f64).cos()).unwrap()
258+
});
259+
black_box(count);
260+
});
261+
b.bytes = $len as u64 * mem::size_of::<u64>() as u64;
262+
}
263+
}
264+
}
263265

264-
#[bench]
265-
fn sort_large_random_expensive(b: &mut Bencher) {
266-
let len = 10000;
267-
b.iter(|| {
268-
let mut v = gen_random(len);
269-
let mut count = 0;
270-
v.sort_by(|a: &u64, b: &u64| {
271-
count += 1;
272-
if count % 1_000_000_000 == 0 {
273-
panic!("should not happen");
274-
}
275-
(*a as f64).cos().partial_cmp(&(*b as f64).cos()).unwrap()
276-
});
277-
black_box(count);
278-
});
279-
b.bytes = len as u64 * mem::size_of::<u64>() as u64;
280-
}
266+
sort!(sort, sort_small_ascending, gen_ascending, 10);
267+
sort!(sort, sort_small_descending, gen_descending, 10);
268+
sort!(sort, sort_small_random, gen_random, 10);
269+
sort!(sort, sort_small_big_random, gen_big_random, 10);
270+
sort!(sort, sort_medium_random, gen_random, 100);
271+
sort!(sort, sort_large_ascending, gen_ascending, 10000);
272+
sort!(sort, sort_large_descending, gen_descending, 10000);
273+
sort!(sort, sort_large_mostly_ascending, gen_mostly_ascending, 10000);
274+
sort!(sort, sort_large_mostly_descending, gen_mostly_descending, 10000);
275+
sort!(sort, sort_large_random, gen_random, 10000);
276+
sort!(sort, sort_large_big_random, gen_big_random, 10000);
277+
sort!(sort, sort_large_strings, gen_strings, 10000);
278+
sort_expensive!(sort_by, sort_large_random_expensive, gen_random, 10000);
279+
280+
sort!(sort_unstable, sort_unstable_small_ascending, gen_ascending, 10);
281+
sort!(sort_unstable, sort_unstable_small_descending, gen_descending, 10);
282+
sort!(sort_unstable, sort_unstable_small_random, gen_random, 10);
283+
sort!(sort_unstable, sort_unstable_small_big_random, gen_big_random, 10);
284+
sort!(sort_unstable, sort_unstable_medium_random, gen_random, 100);
285+
sort!(sort_unstable, sort_unstable_large_ascending, gen_ascending, 10000);
286+
sort!(sort_unstable, sort_unstable_large_descending, gen_descending, 10000);
287+
sort!(sort_unstable, sort_unstable_large_mostly_ascending, gen_mostly_ascending, 10000);
288+
sort!(sort_unstable, sort_unstable_large_mostly_descending, gen_mostly_descending, 10000);
289+
sort!(sort_unstable, sort_unstable_large_random, gen_random, 10000);
290+
sort!(sort_unstable, sort_unstable_large_big_random, gen_big_random, 10000);
291+
sort!(sort_unstable, sort_unstable_large_strings, gen_strings, 10000);
292+
sort_expensive!(sort_unstable_by, sort_unstable_large_random_expensive, gen_random, 10000);

src/libcollections/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#![feature(shared)]
5353
#![feature(slice_get_slice)]
5454
#![feature(slice_patterns)]
55+
#![feature(sort_unstable)]
5556
#![feature(specialization)]
5657
#![feature(staged_api)]
5758
#![feature(str_internals)]

src/libcollections/slice.rs

Lines changed: 128 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,6 +1092,39 @@ impl<T> [T] {
10921092
merge_sort(self, |a, b| a.lt(b));
10931093
}
10941094

1095+
/// Sorts the slice using `compare` to compare elements.
1096+
///
1097+
/// This sort is stable (i.e. does not reorder equal elements) and `O(n log n)` worst-case.
1098+
///
1099+
/// # Current implementation
1100+
///
1101+
/// The current algorithm is an adaptive, iterative merge sort inspired by
1102+
/// [timsort](https://en.wikipedia.org/wiki/Timsort).
1103+
/// It is designed to be very fast in cases where the slice is nearly sorted, or consists of
1104+
/// two or more sorted sequences concatenated one after another.
1105+
///
1106+
/// Also, it allocates temporary storage half the size of `self`, but for short slices a
1107+
/// non-allocating insertion sort is used instead.
1108+
///
1109+
/// # Examples
1110+
///
1111+
/// ```
1112+
/// let mut v = [5, 4, 1, 3, 2];
1113+
/// v.sort_by(|a, b| a.cmp(b));
1114+
/// assert!(v == [1, 2, 3, 4, 5]);
1115+
///
1116+
/// // reverse sorting
1117+
/// v.sort_by(|a, b| b.cmp(a));
1118+
/// assert!(v == [5, 4, 3, 2, 1]);
1119+
/// ```
1120+
#[stable(feature = "rust1", since = "1.0.0")]
1121+
#[inline]
1122+
pub fn sort_by<F>(&mut self, mut compare: F)
1123+
where F: FnMut(&T, &T) -> Ordering
1124+
{
1125+
merge_sort(self, |a, b| compare(a, b) == Less);
1126+
}
1127+
10951128
/// Sorts the slice using `f` to extract a key to compare elements by.
10961129
///
10971130
/// This sort is stable (i.e. does not reorder equal elements) and `O(n log n)` worst-case.
@@ -1122,37 +1155,112 @@ impl<T> [T] {
11221155
merge_sort(self, |a, b| f(a).lt(&f(b)));
11231156
}
11241157

1125-
/// Sorts the slice using `compare` to compare elements.
1158+
/// Sorts the slice, but may not preserve the order of equal elements.
11261159
///
1127-
/// This sort is stable (i.e. does not reorder equal elements) and `O(n log n)` worst-case.
1160+
/// This sort is unstable (i.e. may reorder equal elements), in-place (i.e. does not allocate),
1161+
/// and `O(n log n)` worst-case.
11281162
///
11291163
/// # Current implementation
11301164
///
1131-
/// The current algorithm is an adaptive, iterative merge sort inspired by
1132-
/// [timsort](https://en.wikipedia.org/wiki/Timsort).
1133-
/// It is designed to be very fast in cases where the slice is nearly sorted, or consists of
1134-
/// two or more sorted sequences concatenated one after another.
1165+
/// The current algorithm is based on Orson Peters' [pdqsort][pattern-defeating quicksort],
1166+
/// which is a quicksort variant designed to be very fast on certain kinds of patterns,
1167+
/// sometimes achieving linear time. It is randomized but deterministic, and falls back to
1168+
/// heapsort on degenerate inputs.
11351169
///
1136-
/// Also, it allocates temporary storage half the size of `self`, but for short slices a
1137-
/// non-allocating insertion sort is used instead.
1170+
/// It is generally faster than stable sorting, except in a few special cases, e.g. when the
1171+
/// slice consists of several concatenated sorted sequences.
1172+
///
1173+
/// # Examples
1174+
///
1175+
/// ```
1176+
/// let mut v = [-5, 4, 1, -3, 2];
1177+
///
1178+
/// v.sort_unstable();
1179+
/// assert!(v == [-5, -3, 1, 2, 4]);
1180+
/// ```
1181+
///
1182+
/// [pdqsort]: https://github.com/orlp/pdqsort
1183+
// FIXME #40585: Mention `sort_unstable` in the documentation for `sort`.
1184+
#[unstable(feature = "sort_unstable", issue = "40585")]
1185+
#[inline]
1186+
pub fn sort_unstable(&mut self)
1187+
where T: Ord
1188+
{
1189+
core_slice::SliceExt::sort_unstable(self);
1190+
}
1191+
1192+
/// Sorts the slice using `compare` to compare elements, but may not preserve the order of
1193+
/// equal elements.
1194+
///
1195+
/// This sort is unstable (i.e. may reorder equal elements), in-place (i.e. does not allocate),
1196+
/// and `O(n log n)` worst-case.
1197+
///
1198+
/// # Current implementation
1199+
///
1200+
/// The current algorithm is based on Orson Peters' [pdqsort][pattern-defeating quicksort],
1201+
/// which is a quicksort variant designed to be very fast on certain kinds of patterns,
1202+
/// sometimes achieving linear time. It is randomized but deterministic, and falls back to
1203+
/// heapsort on degenerate inputs.
1204+
///
1205+
/// It is generally faster than stable sorting, except in a few special cases, e.g. when the
1206+
/// slice consists of several concatenated sorted sequences.
11381207
///
11391208
/// # Examples
11401209
///
11411210
/// ```
11421211
/// let mut v = [5, 4, 1, 3, 2];
1143-
/// v.sort_by(|a, b| a.cmp(b));
1212+
/// v.sort_unstable_by(|a, b| a.cmp(b));
11441213
/// assert!(v == [1, 2, 3, 4, 5]);
11451214
///
11461215
/// // reverse sorting
1147-
/// v.sort_by(|a, b| b.cmp(a));
1216+
/// v.sort_unstable_by(|a, b| b.cmp(a));
11481217
/// assert!(v == [5, 4, 3, 2, 1]);
11491218
/// ```
1150-
#[stable(feature = "rust1", since = "1.0.0")]
1219+
///
1220+
/// [pdqsort]: https://github.com/orlp/pdqsort
1221+
// FIXME #40585: Mention `sort_unstable_by` in the documentation for `sort_by`.
1222+
#[unstable(feature = "sort_unstable", issue = "40585")]
11511223
#[inline]
1152-
pub fn sort_by<F>(&mut self, mut compare: F)
1224+
pub fn sort_unstable_by<F>(&mut self, compare: F)
11531225
where F: FnMut(&T, &T) -> Ordering
11541226
{
1155-
merge_sort(self, |a, b| compare(a, b) == Less);
1227+
core_slice::SliceExt::sort_unstable_by(self, compare);
1228+
}
1229+
1230+
/// Sorts the slice using `f` to extract a key to compare elements by, but may not preserve the
1231+
/// order of equal elements.
1232+
///
1233+
/// This sort is unstable (i.e. may reorder equal elements), in-place (i.e. does not allocate),
1234+
/// and `O(n log n)` worst-case.
1235+
///
1236+
/// # Current implementation
1237+
///
1238+
/// The current algorithm is based on Orson Peters' [pdqsort][pattern-defeating quicksort],
1239+
/// which is a quicksort variant designed to be very fast on certain kinds of patterns,
1240+
/// sometimes achieving linear time. It is randomized but deterministic, and falls back to
1241+
/// heapsort on degenerate inputs.
1242+
///
1243+
/// It is generally faster than stable sorting, except in a few special cases, e.g. when the
1244+
/// slice consists of several concatenated sorted sequences.
1245+
///
1246+
/// # Examples
1247+
///
1248+
/// ```
1249+
/// let mut v = [-5i32, 4, 1, -3, 2];
1250+
///
1251+
/// v.sort_unstable_by_key(|k| k.abs());
1252+
/// assert!(v == [1, 2, -3, 4, -5]);
1253+
///
1254+
/// [pdqsort]: https://github.com/orlp/pdqsort
1255+
/// ```
1256+
// FIXME #40585: Mention `sort_unstable_by_key` in the documentation for `sort_by_key`.
1257+
#[unstable(feature = "sort_unstable", issue = "40585")]
1258+
#[inline]
1259+
pub fn sort_unstable_by_key<B, F>(&mut self, f: F)
1260+
where F: FnMut(&T) -> B,
1261+
B: Ord
1262+
{
1263+
core_slice::SliceExt::sort_unstable_by_key(self, f);
11561264
}
11571265

11581266
/// Copies the elements from `src` into `self`.
@@ -1553,28 +1661,20 @@ unsafe fn merge<T, F>(v: &mut [T], mid: usize, buf: *mut T, is_less: &mut F)
15531661
fn merge_sort<T, F>(v: &mut [T], mut is_less: F)
15541662
where F: FnMut(&T, &T) -> bool
15551663
{
1664+
// Slices of up to this length get sorted using insertion sort.
1665+
const MAX_INSERTION: usize = 16;
1666+
// Very short runs are extended using insertion sort to span at least this many elements.
1667+
const MIN_RUN: usize = 8;
1668+
15561669
// Sorting has no meaningful behavior on zero-sized types.
15571670
if size_of::<T>() == 0 {
15581671
return;
15591672
}
15601673

1561-
// FIXME #12092: These numbers are platform-specific and need more extensive testing/tuning.
1562-
//
1563-
// If `v` has length up to `max_insertion`, simply switch to insertion sort because it is going
1564-
// to perform better than merge sort. For bigger types `T`, the threshold is smaller.
1565-
//
1566-
// Short runs are extended using insertion sort to span at least `min_run` elements, in order
1567-
// to improve performance.
1568-
let (max_insertion, min_run) = if size_of::<T>() <= 2 * mem::size_of::<usize>() {
1569-
(64, 32)
1570-
} else {
1571-
(32, 16)
1572-
};
1573-
15741674
let len = v.len();
15751675

15761676
// Short arrays get sorted in-place via insertion sort to avoid allocations.
1577-
if len <= max_insertion {
1677+
if len <= MAX_INSERTION {
15781678
if len >= 2 {
15791679
for i in (0..len-1).rev() {
15801680
insert_head(&mut v[i..], &mut is_less);
@@ -1618,7 +1718,7 @@ fn merge_sort<T, F>(v: &mut [T], mut is_less: F)
16181718

16191719
// Insert some more elements into the run if it's too short. Insertion sort is faster than
16201720
// merge sort on short sequences, so this significantly improves performance.
1621-
while start > 0 && end - start < min_run {
1721+
while start > 0 && end - start < MIN_RUN {
16221722
start -= 1;
16231723
insert_head(&mut v[start..end], &mut is_less);
16241724
}

src/libcollectionstest/slice.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -399,9 +399,10 @@ fn test_sort() {
399399
}
400400
}
401401

402-
// shouldn't panic
403-
let mut v: [i32; 0] = [];
404-
v.sort();
402+
// Should not panic.
403+
[0i32; 0].sort();
404+
[(); 10].sort();
405+
[(); 100].sort();
405406

406407
let mut v = [0xDEADBEEFu64];
407408
v.sort();
@@ -441,13 +442,6 @@ fn test_sort_stability() {
441442
}
442443
}
443444

444-
#[test]
445-
fn test_sort_zero_sized_type() {
446-
// Should not panic.
447-
[(); 10].sort();
448-
[(); 100].sort();
449-
}
450-
451445
#[test]
452446
fn test_concat() {
453447
let v: [Vec<i32>; 0] = [];

0 commit comments

Comments
 (0)