Closed LilithHafner closed 1 year ago
@orlp, would you like to chime in before I merge this?
@LilithHafner Well, you compared against Rust's unstable
sorting algorithm (as well as unstable sorts in other languages). I don't claim glidesort is faster than that on all platforms (although it is on Apple M1). Glidesort is a stable sorting algorithm, and thus competes against [T]::sort
rather than [T]::sort_unstable
, std::stable_sort
in C++, etc.
To avoid misunderstandings, I think the graph should compare glidesort
only with stable
algorithms, if you really want to compare with others tho, an alternative is having all stable
algorithms be a line, and all unstable
be a dotted line, and have a caption explaining it :+1:.
Thanks for your feedback! I want to reserve the dotted/solid distinction for default/primary sorting algorithms vs. non-default/alternate, but I added a note mentioning which algorithms are stable. I think it is acceptable to present this information below the graph rather than on it because
sort
) happens to be stable anywayCan you change the Rust
benchmark to call the binary instead of cargo
, like you do with the c
benchmark?
EDIT: I don't know go
, but I think it's compiled so same applies, probably.
I'm plotting differential execution time. Invocation time is irrelevant. Please read the explanation in the README and/or the implementation. Each language is responsible for timing itself. In the case of rust (and all other languages), I make a single invocation of rust which then loops over various list sizes.
You're right, thanks for your response, if you don't mind I deleted some comments of mine with wrong information about this :smile:.
Sorry for misunderstanding the code, I got confused by what the f
function does.
I think the reason that glidesort performs worse than expected here is the possible panicking/extra checks, in the comparison function.
In the std library unstable_sort
x.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());
and
x.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
only have about a 20% difference in runtime (30% difference in instruction count), on my machine a Ryzen 9 5900X desktop.
However, with glidesort the difference is much more. The panic-able one is 50% slower.
glidesort::sort_by(&mut x, |a, b| a.partial_cmp(b).unwrap());
// 32.97 msec task-clock:u
// 149,158,550 cycles:u
// 491,913,508 instructions:u
// 35,433,567 branches:u
Then if we assume NaN's are equal for the sake of sorting, similarly but not exactly to what Julia does.
glidesort::sort_by(&mut x, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
// 21.65 msec task-clock:u
// 99,184,881 cycles:u
// 305,739,539 instructions:u
// 13,097,776 branches:u
When we don't use a panicking unwrap glidesort ends up faster than the std unstable sort, atleast on my machine.
That proposal gives incorrect answers in the presence of NaN.
use std::cmp::Ordering;
fn main() {
let mut v: [f64; 4] = [4.0, 0.0/0.0, 2.0, 1.0];
println!("Input: {:?}", v);
glidesort::sort_by(&mut v, |a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
println!("Sorted: {:?}", v);
}
Input: [4.0, NaN, 2.0, 1.0]
Sorted: [1.0, 4.0, NaN, 2.0]
See a similar proposal and additional discussion at #6.
The function being benchmarked should be correct for all inputs, even though I don't and can't test all inputs.
There is f64::total_cmp
for ordering floats with NaNs.
Alternatively there's also https://docs.rs/ordered-float/latest/ordered_float/struct.NotNan.html that allows you to check whether all floats are not NaN during a conversion step after which you it uses regular comparison for the floats. This is a lot faster than unwrap
ing on every comparison.
a.total_cmp(b)
and a.partial_cmp(b).unwrap()
have similar performance on this benchmark, at least for the default rust sorting algorithm. Perhaps most of the time is spent elsewhere.
Equally importantly, as I mentioned in #6, this repo is for measuring default sorting algorithms and sometimes non-default sorting algorithms with default settings. Requiring the user to preprocess their input and use a different comparison function does not count as a "default algorithm".
@LilithHafner I think that's fair enough that you want to compare 'the default', but I don't think you're being entirely fair here as to what's allowed as 'the default'.
That proposal gives incorrect answers in the presence of NaN.
It might give incorrect answers in the presence of NaN, but so does your C++ implementation, which doesn't handle NaN at all. In fact, your C++ implementation is worse, it is undefined behavior for inputs with NaNs, and might segfault.
Good catch, thanks! That is definitely an issue, though it is orthogonal to this PR.
If there are no unresolved objections, I'll merge in a few days
glidesort underperforms rust's default sorting algorithm in this benchmark. @orlp, would you like to chime in before I merge this?