From 4903f95ec74270f6f8c1eb787b67f1ffead5a9d2 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Fri, 3 Feb 2017 18:48:28 +0100 Subject: [PATCH 1/9] Unstable sort in libcore --- text/0000-unstable-sort.md | 213 +++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 text/0000-unstable-sort.md diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md new file mode 100644 index 00000000000..4bc5b41ca54 --- /dev/null +++ b/text/0000-unstable-sort.md @@ -0,0 +1,213 @@ +- Feature Name: unstable_sort +- Start Date: 2017-02-03 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Add an unstable sort to libcore. + +# Motivation +[motivation]: #motivation + +At the moment, the only sort function we have in libstd is `slice::sort`. It is stable, +allocates additional memory, and is unavailable in `#![no_std]` environments. + +**Q: What is stability?**
+A: A sort function is stable if it doesn't reorder equal elements. For example: +```rust +let mut orig = vec![(0, 5), (0, 4)]; +let mut v = orig.clone(); + +// Stable sort preserves the original order of equal elements. +v.sort_by_key(|p| p.0); +assert!(orig == v); // OK! + +/// Unstable sort may or may not preserve the original order. +v.unstable_sort_by_key(|p| p.0); +assert!(orig == v); // MAY FAIL! +``` + +**Q: When is stability useful?**
+A: Not very often. A typical example is sorting columns in interactive GUI tables. +If you want to have rows sorted by column X while breaking ties by column Y, you +first have to click on column Y and then click on column X. + +**Q: Can stable sort be performed using unstable sort?**
+A: Yes. If we transform `[T]` into `[(T, usize)]` by pairing every element with it's +index, then perform unstable sort, and finally remove indices, the result will be +equivalent to stable sort. + +**Q: Why is `slice::sort` stable?**
+A: Because stability is a good default. A programmer might call a sort function +without checking in the documentation whether it is stable or unstable. It is very +inutitive to assume stability, so having `slice::sort` perform unstable sorting might +cause unpleasant surprises. + +**Q: Why does `slice::sort` allocate?**
+A: It is possible to implement a non-allocating stable sort, but it would be +considerably slower. + +**Q: Why is `slice::sort` not compatible with `#![no_std]`?**
+A: Because it allocates additional memory. + +**Q: How much faster can unstable sort be?**
+A: Sorting 64-bit integers using [pdqsort][stjepang-pdqsort] (an +unstable sort implementation) is **40% faster** than using `slice::sort`. +Detailed benchmarks are [here](https://github.com/stjepang/pdqsort#extensive-benchmarks). + +**Q: Can unstable sorting benefit from allocation?**
+A: Generally, no. There is no fundamental property in computer science saying so, +but this has always been true in practice. Zero-allocation and instability go +hand in hand. + +Stable sorting, although a good default, is very rarely useful. Users much more often +value higher performance, lower memory overhead, and compatibility with `#![no_std]`. + +Having a really performant, non-allocating sort function in libcore would cover these +needs. At the moment, Rust is compromising on these highly regarded qualities for a +systems programming language by not offering a built-in alternative. + +The API will consist of three functions that mirror the current sort in libstd: + +1. `core::slice::unstable_sort` +2. `core::slice::unstable_sort_by` +3. `core::slice::unstable_sort_by_key` + +By contrast, C++ has functions `std::sort` and `std::stable_sort`, where the +defaults are set up the other way around. + +# Detailed design +[design]: #detailed-design + +Let's see what kinds of unstable sort algorithms other languages most commonly use: + +* C: quicksort +* C++: introsort +* D: introsort +* Swift: introsort +* Go: introsort +* Crystal: introsort +* Java: dual-pivot quicksort + +The most popular sort is definitely introsort. Introsort is an implementation +of quicksort that limits recursion depth. As soon as depth exceeds `2 * log(n)`, +it switches to heapsort in order to guarantee `O(n log n)` worst-case. This +method combines the best of both worlds: great average performance of +quicksort with great worst-case performance of heapsort. + +Java (talking about `Arrays.sort`, not `Collections.sort`) uses dual-pivot +quicksort. It is an improvement of quicksort that chooses two pivots for finer +grained partitioning, offering better performance in practice. + +A very interesting improvement of introsort is [pattern-defeating quicksort][orlp-pdqsort], +offering substantially better performance in common cases. One of the key +tricks pdqsort uses is block partitioning described in paper [BlockQuicksort][blockquicksort]. +This algorithm still hasn't been built into in any programming language's +standard library, but there are plans to include it into some C++ implementations. + +Block partitioning is incompatible with dual-pivot method, but offers much +larger gains in performance. For all these reasons, unstable sort in libcore +will be based on pdqsort. Proposed implementaton is available in +[pdqsort][stjepang-pdqsort] crate. + +**Interface** + +```rust +pub trait SliceExt { + type Item; + + // ... + + fn unstable_sort(&mut self) + where Self::Item: Ord; + + fn unstable_sort_by(&mut self, compare: F) + where F: FnMut(&Self::Item, &Self::Item) -> Ordering; + + fn unstable_sort_by_key(&mut self, mut f: F) + where F: FnMut(&Self::Item) -> B, + B: Ord; +} +``` + +**Examples** + +```rust +let mut v = [-5i32, 4, 1, -3, 2]; + +v.unstable_sort(); +assert!(v == [-5, -3, 1, 2, 4]); + +v.unstable_sort_by(|a, b| b.cmp(a)); +assert!(v == [4, 2, 1, -3, -5]); + +v.unstable_sort_by_key(|k| k.abs()); +assert!(v == [1, 2, -3, 4, -5]); +``` + +In most cases it's sufficient to prepend `sort` with `unstable_` and +instantly benefit from higher performance and lower memory use. + +**Q: Is `slice::sort` ever faster than pdqsort?**
+A: Yes, there are a few cases where it is faster. For example, if the slice +consists of several pre-sorted sequences concatenated one after another, then +`slice::sort` will most probably be faster. But other than that, it should be +generally slower than pdqsort. + +**Q: What about radix sort?**
+A: Radix sort is usually blind to patterns in slices. It treats totally random +and partially sorted the same way. It is probably possible to improve it +by combining it with other techniques until it becomes a hybrid sort. Moreover, +radix sort is incompatible with comparison-based sorting, which makes it +an awkward choice for a general-purpose API. On top of all this, it's +not even that much faster than pdqsort anyway. + +# How We Teach This +[how-we-teach-this]: #how-we-teach-this + +Stability is a confusing and loaded term. Function `slice::unstable_sort` might be +misunderstood as a function that has unstable API. That said, there is no +less confusing alternative to "unstable sorting". Documentation should +clearly state what "stable" and "unstable" mean. + +`slice::unstable_sort` will be mentioned in the documentation for `slice::sort` +as a faster non-allocating alternative. The documentation for +`slice::unstable_sort` must also clearly state that it guarantees no allocation. + +# Drawbacks +[drawbacks]: #drawbacks + +The implementation of sort algorithms will grow bigger, and there will be more +code to review. + +It might be surprising to discover cases where `slice::sort` is faster than +`slice::unstable_sort`. However, these peculiarities can be explained in +documentation. + +# Alternatives +[alternatives]: #alternatives + +Unstable sorting is indistinguishable from stable sorting when sorting +primitive integers. It's possible to specialize `slice::sort` to fall back +to `slice::unstable_sort`. This would improve performance for primitive integers in +most cases, but patching cases type by type with different algorithms makes +performance more inconsistent and less predictable. + +Unstable sort guarantees no allocation. Instead of naming it `slice::unstable_sort`, +it could also be named `slice::noalloc_sort` or `slice::noalloc_unstable_sort`. +This may slightly improve clarity, but feels much more awkward. + +Unstable sort can also be provided as a standalone crate instead of +within the standard library. However, every other systems programming language +has a fast unstable sort in standard library, so why shouldn't Rust, too? + +# Unresolved questions +[unresolved]: #unresolved-questions + +None. + +[orlp-pdqsort]: https://github.com/orlp/pdqsort +[stjepang-pdqsort]: https://github.com/stjepang/pdqsort +[blockquicksort]: http://drops.dagstuhl.de/opus/volltexte/2016/6389/pdf/LIPIcs-ESA-2016-38.pdf From 98bae1377e58a001a2635804fce4026163568563 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Sat, 4 Feb 2017 01:54:07 +0100 Subject: [PATCH 2/9] Fix spelling --- text/0000-unstable-sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 4bc5b41ca54..f91cd1b324a 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -42,7 +42,7 @@ equivalent to stable sort. **Q: Why is `slice::sort` stable?**
A: Because stability is a good default. A programmer might call a sort function without checking in the documentation whether it is stable or unstable. It is very -inutitive to assume stability, so having `slice::sort` perform unstable sorting might +intuitive to assume stability, so having `slice::sort` perform unstable sorting might cause unpleasant surprises. **Q: Why does `slice::sort` allocate?**
From 8b513d0149aa2bc6f23920a11643d77c3fd7bc52 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Sun, 5 Feb 2017 16:17:31 +0100 Subject: [PATCH 3/9] Elaborate and improve wording --- text/0000-unstable-sort.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index f91cd1b324a..108f5c7931d 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -31,8 +31,9 @@ assert!(orig == v); // MAY FAIL! **Q: When is stability useful?**
A: Not very often. A typical example is sorting columns in interactive GUI tables. -If you want to have rows sorted by column X while breaking ties by column Y, you -first have to click on column Y and then click on column X. +E.g. you want to have rows sorted by column X while breaking ties by column Y, so you +first click on column Y and then click on column X. This is a rare case where stable +sorting is important. **Q: Can stable sort be performed using unstable sort?**
A: Yes. If we transform `[T]` into `[(T, usize)]` by pairing every element with it's @@ -57,7 +58,7 @@ A: Sorting 64-bit integers using [pdqsort][stjepang-pdqsort] (an unstable sort implementation) is **40% faster** than using `slice::sort`. Detailed benchmarks are [here](https://github.com/stjepang/pdqsort#extensive-benchmarks). -**Q: Can unstable sorting benefit from allocation?**
+**Q: Can unstable sort benefit from allocation?**
A: Generally, no. There is no fundamental property in computer science saying so, but this has always been true in practice. Zero-allocation and instability go hand in hand. @@ -153,8 +154,11 @@ instantly benefit from higher performance and lower memory use. **Q: Is `slice::sort` ever faster than pdqsort?**
A: Yes, there are a few cases where it is faster. For example, if the slice consists of several pre-sorted sequences concatenated one after another, then -`slice::sort` will most probably be faster. But other than that, it should be -generally slower than pdqsort. +`slice::sort` will most probably be faster. Another case is when using costly +comparison functions, e.g. when sorting strings. `slice::sort` optimizes the +number of comparisons very well, while pdqsort optimizes for fewer writes to +memory at expense of slightly larger number of comparisons. But other than +that, `slice::sort` should be generally slower than pdqsort. **Q: What about radix sort?**
A: Radix sort is usually blind to patterns in slices. It treats totally random From b3e7da27812c4ad0396ba7ccfa4c74f4736b8349 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Sun, 5 Feb 2017 19:02:28 +0100 Subject: [PATCH 4/9] New benchmark numbers --- text/0000-unstable-sort.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 108f5c7931d..53f7e32fb57 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -54,8 +54,8 @@ considerably slower. A: Because it allocates additional memory. **Q: How much faster can unstable sort be?**
-A: Sorting 64-bit integers using [pdqsort][stjepang-pdqsort] (an -unstable sort implementation) is **40% faster** than using `slice::sort`. +A: Sorting 10M 64-bit integers using [pdqsort][stjepang-pdqsort] (an +unstable sort implementation) is **45% faster** than using `slice::sort`. Detailed benchmarks are [here](https://github.com/stjepang/pdqsort#extensive-benchmarks). **Q: Can unstable sort benefit from allocation?**
From 13bf42581873016014c16b0a572479ddc3a060f2 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Tue, 7 Feb 2017 13:51:53 +0100 Subject: [PATCH 5/9] Rename unstable_sort to sort_unstable --- text/0000-unstable-sort.md | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 53f7e32fb57..5f0142e3998 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -25,7 +25,7 @@ v.sort_by_key(|p| p.0); assert!(orig == v); // OK! /// Unstable sort may or may not preserve the original order. -v.unstable_sort_by_key(|p| p.0); +v.sort_unstable_by_key(|p| p.0); assert!(orig == v); // MAY FAIL! ``` @@ -72,9 +72,9 @@ systems programming language by not offering a built-in alternative. The API will consist of three functions that mirror the current sort in libstd: -1. `core::slice::unstable_sort` -2. `core::slice::unstable_sort_by` -3. `core::slice::unstable_sort_by_key` +1. `core::slice::sort_unstable` +2. `core::slice::sort_unstable_by` +3. `core::slice::sort_unstable_by_key` By contrast, C++ has functions `std::sort` and `std::stable_sort`, where the defaults are set up the other way around. @@ -121,13 +121,13 @@ pub trait SliceExt { // ... - fn unstable_sort(&mut self) + fn sort_unstable(&mut self) where Self::Item: Ord; - fn unstable_sort_by(&mut self, compare: F) + fn sort_unstable_by(&mut self, compare: F) where F: FnMut(&Self::Item, &Self::Item) -> Ordering; - fn unstable_sort_by_key(&mut self, mut f: F) + fn sort_unstable_by_key(&mut self, mut f: F) where F: FnMut(&Self::Item) -> B, B: Ord; } @@ -138,17 +138,17 @@ pub trait SliceExt { ```rust let mut v = [-5i32, 4, 1, -3, 2]; -v.unstable_sort(); +v.sort_unstable(); assert!(v == [-5, -3, 1, 2, 4]); -v.unstable_sort_by(|a, b| b.cmp(a)); +v.sort_unstable_by(|a, b| b.cmp(a)); assert!(v == [4, 2, 1, -3, -5]); -v.unstable_sort_by_key(|k| k.abs()); +v.sort_unstable_by_key(|k| k.abs()); assert!(v == [1, 2, -3, 4, -5]); ``` -In most cases it's sufficient to prepend `sort` with `unstable_` and +In most cases it's sufficient to append `_unstable` to `sort` and instantly benefit from higher performance and lower memory use. **Q: Is `slice::sort` ever faster than pdqsort?**
@@ -171,14 +171,14 @@ not even that much faster than pdqsort anyway. # How We Teach This [how-we-teach-this]: #how-we-teach-this -Stability is a confusing and loaded term. Function `slice::unstable_sort` might be +Stability is a confusing and loaded term. Function `slice::sort_unstable` might be misunderstood as a function that has unstable API. That said, there is no less confusing alternative to "unstable sorting". Documentation should clearly state what "stable" and "unstable" mean. -`slice::unstable_sort` will be mentioned in the documentation for `slice::sort` +`slice::sort_unstable` will be mentioned in the documentation for `slice::sort` as a faster non-allocating alternative. The documentation for -`slice::unstable_sort` must also clearly state that it guarantees no allocation. +`slice::sort_unstable` must also clearly state that it guarantees no allocation. # Drawbacks [drawbacks]: #drawbacks @@ -187,7 +187,7 @@ The implementation of sort algorithms will grow bigger, and there will be more code to review. It might be surprising to discover cases where `slice::sort` is faster than -`slice::unstable_sort`. However, these peculiarities can be explained in +`slice::sort_unstable`. However, these peculiarities can be explained in documentation. # Alternatives @@ -195,12 +195,12 @@ documentation. Unstable sorting is indistinguishable from stable sorting when sorting primitive integers. It's possible to specialize `slice::sort` to fall back -to `slice::unstable_sort`. This would improve performance for primitive integers in +to `slice::sort_unstable`. This would improve performance for primitive integers in most cases, but patching cases type by type with different algorithms makes performance more inconsistent and less predictable. -Unstable sort guarantees no allocation. Instead of naming it `slice::unstable_sort`, -it could also be named `slice::noalloc_sort` or `slice::noalloc_unstable_sort`. +Unstable sort guarantees no allocation. Instead of naming it `slice::sort_unstable`, +it could also be named `slice::sort_noalloc` or `slice::sort_unstable_noalloc`. This may slightly improve clarity, but feels much more awkward. Unstable sort can also be provided as a standalone crate instead of From 2bae25ff63c9af6846653161532ede90a97e8f7c Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Sat, 18 Feb 2017 16:32:23 +0100 Subject: [PATCH 6/9] Reorder sections more logically, clearer wording --- text/0000-unstable-sort.md | 88 +++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 5f0142e3998..303a565edf6 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -14,6 +14,13 @@ Add an unstable sort to libcore. At the moment, the only sort function we have in libstd is `slice::sort`. It is stable, allocates additional memory, and is unavailable in `#![no_std]` environments. +Stable sorting, although a good default, is very rarely useful. Users much more often +value higher performance, lower memory overhead, and compatibility with `#![no_std]`. + +Having a really performant, non-allocating sort function in libcore would cover these +needs. At the moment, Rust is compromising on these highly regarded qualities for a +systems programming language by not offering a built-in alternative. + **Q: What is stability?**
A: A sort function is stable if it doesn't reorder equal elements. For example: ```rust @@ -63,12 +70,8 @@ A: Generally, no. There is no fundamental property in computer science saying so but this has always been true in practice. Zero-allocation and instability go hand in hand. -Stable sorting, although a good default, is very rarely useful. Users much more often -value higher performance, lower memory overhead, and compatibility with `#![no_std]`. - -Having a really performant, non-allocating sort function in libcore would cover these -needs. At the moment, Rust is compromising on these highly regarded qualities for a -systems programming language by not offering a built-in alternative. +# Detailed design +[design]: #detailed-design The API will consist of three functions that mirror the current sort in libstd: @@ -79,41 +82,7 @@ The API will consist of three functions that mirror the current sort in libstd: By contrast, C++ has functions `std::sort` and `std::stable_sort`, where the defaults are set up the other way around. -# Detailed design -[design]: #detailed-design - -Let's see what kinds of unstable sort algorithms other languages most commonly use: - -* C: quicksort -* C++: introsort -* D: introsort -* Swift: introsort -* Go: introsort -* Crystal: introsort -* Java: dual-pivot quicksort - -The most popular sort is definitely introsort. Introsort is an implementation -of quicksort that limits recursion depth. As soon as depth exceeds `2 * log(n)`, -it switches to heapsort in order to guarantee `O(n log n)` worst-case. This -method combines the best of both worlds: great average performance of -quicksort with great worst-case performance of heapsort. - -Java (talking about `Arrays.sort`, not `Collections.sort`) uses dual-pivot -quicksort. It is an improvement of quicksort that chooses two pivots for finer -grained partitioning, offering better performance in practice. - -A very interesting improvement of introsort is [pattern-defeating quicksort][orlp-pdqsort], -offering substantially better performance in common cases. One of the key -tricks pdqsort uses is block partitioning described in paper [BlockQuicksort][blockquicksort]. -This algorithm still hasn't been built into in any programming language's -standard library, but there are plans to include it into some C++ implementations. - -Block partitioning is incompatible with dual-pivot method, but offers much -larger gains in performance. For all these reasons, unstable sort in libcore -will be based on pdqsort. Proposed implementaton is available in -[pdqsort][stjepang-pdqsort] crate. - -**Interface** +### Interface ```rust pub trait SliceExt { @@ -133,7 +102,7 @@ pub trait SliceExt { } ``` -**Examples** +### Examples ```rust let mut v = [-5i32, 4, 1, -3, 2]; @@ -148,8 +117,39 @@ v.sort_unstable_by_key(|k| k.abs()); assert!(v == [1, 2, -3, 4, -5]); ``` -In most cases it's sufficient to append `_unstable` to `sort` and -instantly benefit from higher performance and lower memory use. +### Implementation + +Proposed implementaton is available in the [pdqsort][stjepang-pdqsort] crate. + +**Q: Why choose this particular sort algorithm?**
+A: First, let's see what unstable sort algorithms other languages use: + +* C: quicksort +* C++: introsort +* D: introsort +* Swift: introsort +* Go: introsort +* Crystal: introsort +* Java: dual-pivot quicksort + +The most popular sort is definitely introsort. Introsort is an implementation +of quicksort that limits recursion depth. As soon as depth exceeds `2 * log(n)`, +it switches to heapsort in order to guarantee `O(n log n)` worst-case. This +method combines the best of both worlds: great average performance of +quicksort with great worst-case performance of heapsort. + +Java (talking about `Arrays.sort`, not `Collections.sort`) uses dual-pivot +quicksort. It is an improvement of quicksort that chooses two pivots for finer +grained partitioning, offering better performance in practice. + +A very interesting improvement of introsort is [pattern-defeating quicksort][orlp-pdqsort], +which is substantially faster in common cases. One of the key tricks pdqsort +uses is block partitioning described in the [BlockQuicksort][blockquicksort] paper. +This algorithm still hasn't been built into in any programming language's +standard library, but there are plans to include it into some C++ implementations. + +Among all these, pdqsort is the clear winner. Some benchmarks are available +[here](https://github.com/stjepang/pdqsort#a-simple-benchmark). **Q: Is `slice::sort` ever faster than pdqsort?**
A: Yes, there are a few cases where it is faster. For example, if the slice From c40dc37ee3e11473859a22f023bf0e3ada307215 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Sun, 26 Feb 2017 18:15:52 +0100 Subject: [PATCH 7/9] Rename feature to sort_unstable --- text/0000-unstable-sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 303a565edf6..7a1a8ebe6bf 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -1,4 +1,4 @@ -- Feature Name: unstable_sort +- Feature Name: sort_unstable - Start Date: 2017-02-03 - RFC PR: (leave this empty) - Rust Issue: (leave this empty) From 7d2e9403377aa326fe6a715c97916ae25f9e6141 Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Wed, 8 Mar 2017 10:58:13 +0100 Subject: [PATCH 8/9] Improve wording per brson's request --- text/0000-unstable-sort.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 7a1a8ebe6bf..1057830c4bb 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -14,12 +14,14 @@ Add an unstable sort to libcore. At the moment, the only sort function we have in libstd is `slice::sort`. It is stable, allocates additional memory, and is unavailable in `#![no_std]` environments. -Stable sorting, although a good default, is very rarely useful. Users much more often -value higher performance, lower memory overhead, and compatibility with `#![no_std]`. +The sort function is stable, which is a good but conservative default. However, +stability is rarely a required property in practice, and some other characteristics +of sort algorithms like higher performance or lower memory overhead are often more +desirable. -Having a really performant, non-allocating sort function in libcore would cover these -needs. At the moment, Rust is compromising on these highly regarded qualities for a -systems programming language by not offering a built-in alternative. +Having a performant, non-allocating unstable sort function in libcore would cover those +needs. At the moment Rust is not offering a built-in alternative (only crates), which +is unusual for a systems programming language. **Q: What is stability?**
A: A sort function is stable if it doesn't reorder equal elements. For example: @@ -39,8 +41,8 @@ assert!(orig == v); // MAY FAIL! **Q: When is stability useful?**
A: Not very often. A typical example is sorting columns in interactive GUI tables. E.g. you want to have rows sorted by column X while breaking ties by column Y, so you -first click on column Y and then click on column X. This is a rare case where stable -sorting is important. +first click on column Y and then click on column X. This is a use case where stability +is important. **Q: Can stable sort be performed using unstable sort?**
A: Yes. If we transform `[T]` into `[(T, usize)]` by pairing every element with it's @@ -122,7 +124,7 @@ assert!(v == [1, 2, -3, 4, -5]); Proposed implementaton is available in the [pdqsort][stjepang-pdqsort] crate. **Q: Why choose this particular sort algorithm?**
-A: First, let's see what unstable sort algorithms other languages use: +A: First, let's analyse what unstable sort algorithms other languages use: * C: quicksort * C++: introsort @@ -142,7 +144,7 @@ Java (talking about `Arrays.sort`, not `Collections.sort`) uses dual-pivot quicksort. It is an improvement of quicksort that chooses two pivots for finer grained partitioning, offering better performance in practice. -A very interesting improvement of introsort is [pattern-defeating quicksort][orlp-pdqsort], +A recent improvement of introsort is [pattern-defeating quicksort][orlp-pdqsort], which is substantially faster in common cases. One of the key tricks pdqsort uses is block partitioning described in the [BlockQuicksort][blockquicksort] paper. This algorithm still hasn't been built into in any programming language's @@ -163,7 +165,7 @@ that, `slice::sort` should be generally slower than pdqsort. **Q: What about radix sort?**
A: Radix sort is usually blind to patterns in slices. It treats totally random and partially sorted the same way. It is probably possible to improve it -by combining it with other techniques until it becomes a hybrid sort. Moreover, +by combining it with some other techniques, but it's not trivial. Moreover, radix sort is incompatible with comparison-based sorting, which makes it an awkward choice for a general-purpose API. On top of all this, it's not even that much faster than pdqsort anyway. @@ -183,8 +185,8 @@ as a faster non-allocating alternative. The documentation for # Drawbacks [drawbacks]: #drawbacks -The implementation of sort algorithms will grow bigger, and there will be more -code to review. +The amount of code for sort algorithms will grow, and there will be more code +to review. It might be surprising to discover cases where `slice::sort` is faster than `slice::sort_unstable`. However, these peculiarities can be explained in From d25e48329c394a92b2e2c25f45b67f5abb70383f Mon Sep 17 00:00:00 2001 From: Stjepan Glavina Date: Wed, 8 Mar 2017 14:17:30 +0100 Subject: [PATCH 9/9] Link to an instability-bit-me story --- text/0000-unstable-sort.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-unstable-sort.md b/text/0000-unstable-sort.md index 1057830c4bb..1be132815ee 100644 --- a/text/0000-unstable-sort.md +++ b/text/0000-unstable-sort.md @@ -20,7 +20,7 @@ of sort algorithms like higher performance or lower memory overhead are often mo desirable. Having a performant, non-allocating unstable sort function in libcore would cover those -needs. At the moment Rust is not offering a built-in alternative (only crates), which +needs. At the moment Rust is not offering this solution as a built-in (only crates), which is unusual for a systems programming language. **Q: What is stability?**
@@ -54,6 +54,8 @@ A: Because stability is a good default. A programmer might call a sort function without checking in the documentation whether it is stable or unstable. It is very intuitive to assume stability, so having `slice::sort` perform unstable sorting might cause unpleasant surprises. +See this [story](https://medium.com/@cocotutch/a-swift-sorting-problem-e0ebfc4e46d4#.yfvsgjozx) +for an example. **Q: Why does `slice::sort` allocate?**
A: It is possible to implement a non-allocating stable sort, but it would be