在这段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%。
答案 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 ,这对我来说绝对是胜利。