【问题标题】:Fastest way to sort in Python (no cython)在 Python 中排序的最快方法(无 cython)
【发布时间】:2018-10-16 19:14:12
【问题描述】:

我有一个问题,我必须使用自定义函数对一个非常大的数组(形状 - 7900000X4X4)进行排序。我用了sorted,但排序花了1个多小时。我的代码是这样的。

def compare(x,y):
    print('DD '+str(x[0]))
    if(np.array_equal(x[1],y[1])==True):
        return -1
    a = x[1].flatten()
    b = y[1].flatten()
    idx = np.where( (a>b) != (a<b) )[0][0]
    if a[idx]<0 and b[idx]>=0:
        return 0
    elif b[idx]<0 and a[idx]>=0:
        return 1
    elif a[idx]<0 and b[idx]<0:
        if a[idx]>b[idx]:
            return 0
        elif a[idx]<b[idx]:
            return 1
    elif a[idx]<b[idx]:
        return 1
    else:
        return 0
def cmp_to_key(mycmp):
    class K:
        def __init__(self, obj, *args):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj)
    return K
tblocks = sorted(tblocks.items(),key=cmp_to_key(compare))

这很有效,但我希望它在几秒钟内完成。我认为 python 中的任何直接实现都不能给我所需的性能,所以我尝试了 cython。我的 Cython 代码是这样的,非常简单。

cdef int[:,:] arrr
cdef int size

cdef bool compare(int a,int b):
    global arrr,size
    cdef int[:] x = arrr[a]
    cdef int[:] y = arrr[b]
    cdef int i,j
    i = 0
    j = 0
    while(i<size):
        if((j==size-1)or(y[j]<x[i])):
            return 0
        elif(x[i]<y[j]):
            return 1
        i+=1
        j+=1
    return (j!=size-1)

def sorted(np.ndarray boxes,int total_blocks,int s):
    global arrr,size
    cdef int i
    cdef vector[int] index = xrange(total_blocks)
    arrr = boxes
    size = s
    sort(index.begin(),index.end(),compare)
    return index

cython 中的这段代码耗时 33 秒! Cython 是解决方案,但我正在寻找一些可以直接在 python 上运行的替代解决方案。例如麻木。我尝试了 Numba,但没有得到令人满意的结果。请帮忙!

【问题讨论】:

  • 如果你想让我们做的不仅仅是阅读代码,你需要提供一些测试数据。 items 方法表明 tblocks 是一个字典。这些值是某种类型和/或维度的数组?
  • dtype 是什么数组?此外,对我来说,您的 Python 和 Cython 比较函数如何等效并不明显。
  • 我想知道是否有使用值而不是 cmp 的等效方法。
  • 那么 box 真的是一个 (7900000X4X4) 数组吗?你能提供一个最小的工作示例吗?这应该是使用自定义排序功能的方法:github.com/numba/numba/blob/master/numba/targets/quicksort.py

标签: python performance sorting numpy cython


【解决方案1】:

如果没有工作示例,很难给出答案。我假设,你的 Cython 代码中的 arrr 是一个二维数组,我假设它的大小是 size=arrr.shape[0]

Numba 实现

import numpy as np
import numba as nb
from numba.targets import quicksort


def custom_sorting(compare_fkt):
  index_arange=np.arange(size)

  quicksort_func=quicksort.make_jit_quicksort(lt=compare_fkt,is_argsort=False)
  jit_sort_func=nb.njit(quicksort_func.run_quicksort)
  index=jit_sort_func(index_arange)

  return index

def compare(a,b):
    x = arrr[a]
    y = arrr[b]
    i = 0
    j = 0
    while(i<size):
        if((j==size-1)or(y[j]<x[i])):
            return False
        elif(x[i]<y[j]):
            return True
        i+=1
        j+=1
    return (j!=size-1)


arrr=np.random.randint(-9,10,(7900000,8))
size=arrr.shape[0]

index=custom_sorting(compare)

这为生成的测试数据提供了 3.85 秒。但是排序算法的速度很大程度上取决于数据......

简单示例

import numpy as np
import numba as nb
from numba.targets import quicksort

#simple reverse sort
def compare(a,b):
  return a > b

#create some test data
arrr=np.array(np.random.rand(7900000)*10000,dtype=np.int32)
#we can pass the comparison function
quicksort_func=quicksort.make_jit_quicksort(lt=compare,is_argsort=True)
#compile the sorting function
jit_sort_func=nb.njit(quicksort_func.run_quicksort)
#get the result
ind_sorted=jit_sort_func(arrr)

这个实现比 np.argsort 慢大约 35%,但这在编译代码中使用 np.argsort 时也很常见。

【讨论】:

    【解决方案2】:

    如果我正确理解了您的代码,那么您心中的顺序就是标准顺序,只是它从 0 开始环绕在 +/-infinity 并在 -0 达到最大值。最重要的是,我们有简单的从左到右的字典顺序。

    现在,如果您的数组 dtype 是整数,请注意以下事项:由于负数视图转换为 unsigned int 的补码表示,使您的订单成为标准订单。最重要的是,如果我们使用大端编码,则可以通过将视图转换为void dtype 来实现高效的字典排序。

    下面的代码显示,使用10000x4x4 示例,此方法给出的结果与您的 Python 代码相同。

    它还在7,900,000x4x4 示例上对其进行了基准测试(使用数组,而不是字典)。在我普通的笔记本电脑上,这种方法需要8 秒。

    import numpy as np
    
    def compare(x, y):
    #    print('DD '+str(x[0]))
        if(np.array_equal(x[1],y[1])==True):
            return -1
        a = x[1].flatten()
        b = y[1].flatten()
        idx = np.where( (a>b) != (a<b) )[0][0]
        if a[idx]<0 and b[idx]>=0:
            return 0
        elif b[idx]<0 and a[idx]>=0:
            return 1
        elif a[idx]<0 and b[idx]<0:
            if a[idx]>b[idx]:
                return 0
            elif a[idx]<b[idx]:
                return 1
        elif a[idx]<b[idx]:
            return 1
        else:
            return 0
    def cmp_to_key(mycmp):
        class K:
            def __init__(self, obj, *args):
                self.obj = obj
            def __lt__(self, other):
                return mycmp(self.obj, other.obj)
        return K
    
    def custom_sort(a):
        assert a.dtype==np.int64
        b = a.astype('>i8', copy=False)
        return b.view(f'V{a.dtype.itemsize * a.shape[1]}').ravel().argsort()
    
    tblocks = np.random.randint(-9,10, (10000, 4, 4))
    tblocks = dict(enumerate(tblocks))
    
    tblocks_s = sorted(tblocks.items(),key=cmp_to_key(compare))
    
    tblocksa = np.array(list(tblocks.values()))
    tblocksa = tblocksa.reshape(tblocksa.shape[0], -1)
    order = custom_sort(tblocksa)
    tblocks_s2 = list(tblocks.items())
    tblocks_s2 = [tblocks_s2[o] for o in order]
    
    print(tblocks_s == tblocks_s2)
    
    from timeit import timeit
    
    data = np.random.randint(-9_999, 10_000, (7_900_000, 4, 4))
    
    print(timeit(lambda: data[custom_sort(data.reshape(data.shape[0], -1))],
                 number=5) / 5)
    

    样本输出:

    True
    7.8328493310138585
    

    【讨论】:

      猜你喜欢
      • 2011-04-20
      • 2012-11-16
      • 2014-07-19
      • 2016-08-30
      • 2018-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多