Beating Up on Qsort (2019)
travisdowns.github.io
travisdowns.github.io
I recently did my own benchmarking on various qsort()s since I was trying to implement a faster one. The various BSDs and macOS qsort() are all faster than glibc at sorting integers and they don't allocate memory:
https://github.com/ludocode/pottery/tree/master/examples/pot...
Of course sorting is much faster if you can inline the comparator so a templated sort algorithm is always going to be faster than a function that takes a function pointer. But this does not require C++; it can be done in plain C. The templated intro_sort from Pottery (linked above) is competitive with std::sort, as are the excellent swensort/sort templates:
In particular, I tried to modify glibc's qsort to force inlining of the comparator, using both flatten and always_inline attributes, but failed probably because of the recursive nature of merge sort: the main sorting function is recursive, meaning it cannot be flattened without limit, which ends up inhibiting inlining of the comparator as well.
It can definitely be made to work, but it's less automatic than C++ and compatators passed as template arguments. Of course, the "specialize everything" approach of C++ has plenty of downsides too!
---
[1] Really, what you get is guaranteed specialization, and then inlining follows easily from that, if the compiler decides it is profitable to inline the small comparator into the sort function.
https://github.com/orlp/pdqsort
Here are some of the results on an Ivy Bridge hackintosh:
size, qsort, inline, sort, stable, pdqsort, radix7
1000, 88.0, 60.6, 31.2, 37.5, 24.6, 12.8
10000, 109.3, 77.1, 45.6, 51.6, 29.1, 12.0
100000, 137.1, 97.7, 57.7, 71.9, 33.3, 12.5
1000000, 159.6, 114.2, 70.6, 88.6, 39.7, 20.4
10000000, 185.2, 133.7, 82.0, 99.7, 43.6, 19.4
Edit: it's not quite as easy to plug radix7 into pdqsort's more varied benchmark program, which expects a template function with a type parameter of int.Admittedly, the benchmark I am using is very simple. In general, radix sort is sensitive to very different characteristics of the input than almost any comparison based sort. E.g., LSD radix sorts (at least like the ones presented here) care about the full range of the input, or how many bits are non-zero in any key (or some similar thing) since the number of passes is related to the number of relevant bits.
Similarly, certain patterns that are good for quicksort or merge sort are terrible for radix sort and vice versa. So it's hard to crown an overall winner: it is input dependent.