将Radix Sort(和python)推到极限

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将Radix Sort(和python)推到极限相关的知识,希望对你有一定的参考价值。

我对Web上的许多python radix实现的排序感到非常沮丧。

它们始终使用10的基数,并通过除以10的幂或取数字的log10来获得它们迭代的数字的数字。这是非常低效的,因为与位移相比,log10不是特别快的操作,这几乎快了100倍!

更高效的实现使用256的基数并逐字节地对数字进行排序。这允许使用可笑的快速位运算符完成所有“字节获取”。不幸的是,似乎绝对没有人在python中实现了使用位运算符而不是对数的基数排序。

因此,我亲自动手并想出了这只野兽,它的运行速度大约是小型阵列的一半,并且在大型阵列上的运行速度几乎一样快(例如len大约10,000,000):

import itertools

def radix_sort(unsorted):
    "Fast implementation of radix sort for any size num."
    maximum, minimum = max(unsorted), min(unsorted)

    max_bits = maximum.bit_length()
    highest_byte = max_bits // 8 if max_bits % 8 == 0 else (max_bits // 8) + 1

    min_bits = minimum.bit_length()
    lowest_byte = min_bits // 8 if min_bits % 8 == 0 else (min_bits // 8) + 1

    sorted_list = unsorted
    for offset in xrange(lowest_byte, highest_byte):
        sorted_list = radix_sort_offset(sorted_list, offset)

    return sorted_list

def radix_sort_offset(unsorted, offset):
    "Helper function for radix sort, sorts each offset."
    byte_check = (0xFF << offset*8)

    buckets = [[] for _ in xrange(256)]

    for num in unsorted:
        byte_at_offset = (num & byte_check) >> offset*8
        buckets[byte_at_offset].append(num)

    return list(itertools.chain.from_iterable(buckets))

这个版本的基数排序通过查找它必须排序的字节来工作(如果你只传递256以下的整数,它只会排序一个字节,等等)然后将每个字节从LSB中排序,将它们按顺序转储到桶中然后只是将桶连在一起。对需要排序的每个字节重复此操作,并在O(n)时间内获得排序良好的数组。

然而,它并没有它可能的那么快,而且在我把它作为一个比其他所有基数排序更好的基数排序之前,我想让它更快。

在这上面运行cProfile告诉我在append方法上花了很多时间用于列表,这让我觉得这个块:

    for num in unsorted:
        byte_at_offset = (num & byte_check) >> offset*8
        buckets[byte_at_offset].append(num)

radix_sort_offset吃了很多时间。这也是一个块,如果你真的看它,它可以完成90%的工作。这段代码看起来可能是numpy-ized,我认为这会带来相当大的性能提升。不幸的是,我对numpy更复杂的功能并不是很好,所以无法弄清楚这一点。非常感谢帮助。

我目前正在使用itertools.chain.from_iterable来压扁buckets,但如果有人有更快的建议,我相信它也会有所帮助。

最初,我有一个get_byte函数返回一个数字的nth字节,但内联代码给了我一个巨大的速度提升所以我做到了。

关于实施的任何其他评论或挤出更多性能的方法也受到赞赏。我想听到你所拥有的一切和一切。

答案

你已经意识到了

for num in unsorted:
    byte_at_offset = (num & byte_check) >> offset*8
    buckets[byte_at_offset].append(num)

大部分时间都在哪里 - 好;-)

加速这种事情有两种标准技巧,都与从循环中移动不变量有关:

  1. 在循环外计算“offset * 8”。将其存储在局部变量中。每次迭代保存乘法。
  2. 在循环外添加bucketappender = [bucket.append for bucket in buckets]。每次迭代保存方法查找。

结合它们,循环看起来像:

for num in unsorted:
    bucketappender[(num & byte_check) >> ofs8](num)

将其折叠为一个语句还会在每次迭代时保存一对本地vrbl存储/获取操作码。

但是,在更高的层次上,加速基数排序的标准方法是使用更大的基数。什么是神奇的256?没什么,除了它便于位移。但512,1024,2048也是如此......这是一个经典的时间/空间权衡。

PS:很长的数字,

(num >> offset*8) & 0xff

会运行得更快。这是因为你的num & byte_check需要时间与log(num)成比例 - 它通常必须创建一个与num一样大的整数。

另一答案

这是一个旧的线程,但我在寻找radix排序一组正整数时遇到了这个问题。我试图看看我是否可以做比已经邪恶的快速timsort(再次给你的帽子蒂姆·彼得斯)做得更好,它实现了python的内置排序和排序!要么我不理解上述代码的某些方面,或者如果我这样做,上面提到的代码有一些问题恕我直言。

  1. 它只对从最小项的最高字节开始并以最大项的最高字节结束的字节进行排序。在某些特殊数据的情况下,这可能没问题。但总的来说,该方法无法区分由于较低位而不同的项目。例如: arr=[65535,65534] radix_sort(arr) 产生错误的输出: [65535, 65534]
  2. 用于循环辅助函数的范围不正确。我的意思是如果lowest_byte和highest_byte相同,则完全跳过辅助函数的执行。顺便说一句,我不得不将xrange改为2个范围内的范围。
  3. 通过修改以解决上述两点,我得到了它的工作。但它需要10-20倍的python内置排序或排序时间!我知道timsort非常有效,并且利用了数据中已经排序的运行。但我试图看看我是否可以使用先前的知识,我的数据都是正整数,在我的排序中有一些优势。与timsort相比,为什么基数排序如此糟糕?我使用的数组大小约为80K项。是因为除了算法效率之外,timsort实现还具有源于可能使用低级库的其他效率吗?或者我完全错过了什么?我使用的修改代码如下: import itertools def radix_sort(unsorted): "Fast implementation of radix sort for any size num." maximum, minimum = max(unsorted), min(unsorted) max_bits = maximum.bit_length() highest_byte = max_bits // 8 if max_bits % 8 == 0 else (max_bits // 8) + 1 # min_bits = minimum.bit_length() # lowest_byte = min_bits // 8 if min_bits % 8 == 0 else (min_bits // 8) + 1 sorted_list = unsorted # xrange changed to range, lowest_byte deleted from the arguments for offset in range(highest_byte): sorted_list = radix_sort_offset(sorted_list, offset) return sorted_list def radix_sort_offset(unsorted, offset): "Helper function for radix sort, sorts each offset." byte_check = (0xFF << offset*8) # xrange changed to range buckets = [[] for _ in range(256)] for num in unsorted: byte_at_offset = (num & byte_check) >> offset*8 buckets[byte_at_offset].append(num) return list(itertools.chain.from_iterable(buckets))
另一答案

您可以简单地使用现有的C或C ++实现之一,例如来自integer_sortBoost.Sort或来自u4_sortusort。从Python调用本机C或C ++代码非常容易,请参阅How to sort an array of integers faster than quicksort?

我完全感到沮丧。虽然已经超过2年了,numpy still does not have radix sort。我会让NumPy开发人员知道他们可以简单地抓住现有的一个实现;许可不应成为问题。

以上是关于将Radix Sort(和python)推到极限的主要内容,如果未能解决你的问题,请参考以下文章

排序算法:Radix Sort 基数排序

桶排序/基数排序(Radix Sort)

大数定律和中心极限定律

Radix Sort

Counting Sort and Radix Sort

基数排序(radix sort)