在数组的两个不同部分中计算重复整数的最快方法

时间:2018-10-03 21:59:58

标签: python algorithm performance

在这段Python代码中,

fun遍历数组arr,并为每个节对计算两个数组节中相同整数的数量。 (它模拟一个矩阵。)这样总共进行了n*(n-1)/2*m个比较,时间复杂度为O(n^2)

是否存在编程解决方案或解决此问题的方法,这些方法或方法可以产生相同的结果,但时间复杂度降低了?

# n > 500000, 0 < i < n, m = 100
# dim(arr) = n*m, 0 < arr[x] < 4294967311

arr = mp.RawArray(ctypes.c_uint, n*m)

def fun(i):
    for j in range(i-1,0,-1):
        count = 0
        for k in range(0,m):
            count += (arr[i*m+k] == arr[j*m+k])
        if count/m > 0.7:
            return (i,j)
    return ()
  • arr是一个共享内存阵列,因此出于简单性和性能原因,最好将其保持只读状态。

  • arr被实现为multiprocessing中的一维RawArray。根据我的测试,到目前为止,它具有最快的性能。例如,使用numpy 2D数组:

    arr = np.ctypeslib.as_array(mp.RawArray(ctypes.c_uint, n*m)).reshape(n,m)
    

    将提供矢量化功能,但将总运行时间增加一个数量级-n = 1500时为250s而不是30s,总计为 733%

2 个答案:

答案 0 :(得分:1)

由于您根本无法更改数组特征,因此我认为您一直受困于 O(n ^ 2)numpy将获得一些矢量化,但会更改其他共享数组的访问。从最里面的操作开始:

    for k in range(0,m):
        count += (arr[i][k] == arr[j][k])

将其更改为单行分配:

    count = sum(arr[i][k] == arr[j][k] for k in range(m))

现在,如果这是一个 truly 数组,而不是列表列表,请使用数组包的向量化来简化循环,一次一次:

    count = sum(arr[i] == arr[j])   # results in a vector of counts

您现在可以在j处返回count[j] / m > 0.7索引。请注意,并不需要真正为每个返回i:它在函数中是常量,并且调用程序已经具有该值。您的数组包可能有一对向量化索引操作,可以返回这些索引。如果您使用的是numpy,这些内容很容易在此站点上查找。

答案 1 :(得分:1)

因此,在进行了一些修改之后,我能够在NumPy的向量化和Numba的JIT编译器的帮助下大大减少运行时间。回到原始代码:

arr = mp.RawArray(ctypes.c_uint, n*m)

def fun(i):
    for j in range(i-1,0,-1):
        count = 0
        for k in range(0,m):
            count += (arr[i*m+k] == arr[j*m+k])
        if count/m > 0.7:
            return (i,j)
return ()

我们可以省去最下面的return语句,也可以完全放弃使用count的想法,而只需:

def fun(i):
    for j in range(i-1,0,-1):
        if sum(arr[i*m+k] == arr[j*m+k] for k in range(m)) > 0.7*m:
            return (i,j)

然后,我们将数组arr更改为NumPy格式:

np_arr = np.frombuffer(arr,dtype='int32').reshape(m,n)

这里要注意的重要一点是,我们不会将NumPy数组用作要从多个进程写入的共享内存数组,以避免开销陷阱。

最后,我们应用Numba的装饰器,并以矢量形式重写sum函数,使其与新数组一起工作:

import numba as nb
@nb.njit(fastmath=True,parallel=True)
def fun(i):
    for j in range(i-1, 0, -1):
        if np.sum(np_arr[i] == np_arr[j]) > 0.7*m:
            return (i,j)

这将运行时间缩短至 7.9s ,这对我来说绝对是胜利。