When should we use Radix sort?

前端 未结 11 1850
孤街浪徒
孤街浪徒 2021-01-30 16:27

It seems Radix sort has a very good average case performance, i.e. O(kN): http://en.wikipedia.org/wiki/Radix_sort

Yet it seems like most people are still using

11条回答
  •  栀梦
    栀梦 (楼主)
    2021-01-30 16:52

    Radix sort isn't a comparison-based sort and can only sort numeric types like integers (including pointer addresses) and floating-point, and it's a bit difficult to portably support floating-point.

    It's probably because it has such a narrow range of applicability that many standard libraries choose to omit it. It can't even let you provide your own comparator, since some people might not want to even sort integers directly so much as using the integers as indices to something else to be used as a key for sorting, e.g. Comparison-based sorts allow all that flexibility so it's probably a case of just preferring a generalized solution fitting 99% of people's daily needs instead of going out of the way to cater to that 1%.

    That said, in spite of the narrow applicability, in my domain I find more use for radix sorts than introsorts or quicksorts. I'm in that 1% and barely ever work with, say, string keys, but often find use cases for numbers that benefit from being sorted. It's because my codebase revolves around indices to entities and components (entity-component system) as well as things like indexed meshes and there's a whole lot of numeric data.

    As a result, radix sort becomes useful for all kinds of things in my case. One common example in my case is eliminating duplicate indices. In that case I don't really need the results to be sorted but often a radix sort can eliminate duplicates faster than the alternatives.

    Another is finding, say, a median split for a kd-tree along a given dimension. There radix sorting the floating-point values of the point for a given dimension gives me a median position rapidly in linear time to split the tree node.

    Another is depth-sorting higher-level primitives by z for semi-proper alpha transparency if we aren't going to be doing it in a frag shader. That also applies to GUIs and vector graphics software to z-order elements.

    Another is cache-friendly sequential access using a list of indices. If the indices are traversed many times, it often improves performance if I radix sort them in advance so that the traversal is done in sequential order instead of random order. The latter could zig-zag back and forth in memory, evicting data from cache lines only to reload the same memory region repeatedly within the same loop. When I radix sort the indices first prior to accessing them repeatedly, that ceases to happen and I can reduce cache misses considerably. This is actually my most common use for radix sorts and it's the key to my ECS being cache-friendly when systems want to access entities with two or more components.

    In my case I have a multithreaded radix sort which I use quite often. Some benchmarks:

    --------------------------------------------
    - test_mt_sort
    --------------------------------------------
    Sorting 1,000,000 elements 32 times...
    
    mt_radix_sort: {0.234000 secs}
    -- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
    
    std::sort: {1.778000 secs}
    -- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
    
    qsort: {2.730000 secs}
    -- small result: [ 22 48 59 77 79 80 84 84 93 98 ]
    

    I can average something like 6-7 ms to sort a million numbers one time on my dinky hardware which isn't as fast as I would like since 6-7 milliseconds can still be noticed by users sometimes in interactive contexts, but still a whole lot better than 55-85 ms as with the case of C++'s std::sort or C's qsort which would definitely lead to very obvious hiccups in frame rates. I've even heard of people implementing radix sorts using SIMD, though I have no idea how they managed that. I'm not smart enough to come up with such a solution, though even my naive little radix sort does quite well compared to the standard libraries.

提交回复
热议问题