我有很多非常大的矩阵AFeatures
,我正在使用欧几里得距离将它们与其他一些非常大的阵BFeatures
进行比较,它们都具有(878, 2, 4, 15, 17, 512)
的形状。我正试图将这个过程平行化,以加快比较的速度。我在Conda环境中使用Python3,我的原始代码平均使用两个CPU内核(100%(:
per_slice_comparisons = np.zeros(shape=(878, 878, 2, 4))
for i in range(878):
for j in range(878):
for k in range(2):
for l in range(4):
per_slice_comparisons[i, j, k, l] = np.linalg.norm(AFeatures[i, k, l, :] - BFeatures[j, k, l, :])
我已经尝试了两种方法来加速代码。
使用多处理
def fill_array(i): comparisons = np.zeros(shape=(878, 2, 4)) for j in range(878): for k in range(2): for l in range(4): comparisons[j, k, l] = np.linalg.norm(AFeatures[i, k, l, :] -BFeatures[j, k, l, :]) comparisons[j, k, l] = 0 return comparisons pool = Pool(processes=6) list_start_vals = range(878) per_slice_comparisons = np.array(pool.map(fill_array, list_start_vals)) pool.close()
这种方法将运行时间增加了约5%,尽管所有8个CPU核心现在都在100%使用。我尝试了许多不同的过程,过程越多,速度就越慢。
这是一种略有不同的方法,我使用numexpr库来进行更快的线性范数运算。对于单个操作,这种方法将运行时间减少了10倍。
os.environ['NUMEXPR_MAX_THREADS'] = '8' os.environ['NUMEXPR_NUM_THREADS'] = '4' import numexpr as ne def linalg_norm(a): sq_norm = ne.evaluate('sum(a**2)') return ne.evaluate('sqrt(sq_norm)') per_slice_comparisons = np.zeros(shape=(878, 878, 2, 4)) for i in range(878): for j in range(878): for k in range(2): for l in range(4): per_slice_comparisons[i, j, k, l] = linalg_norm(AFeatures[i, k, l, :] - BFeatures[j, k, l, :])
但是,对于嵌套的for循环,这种方法将总执行时间增加了3倍。我不明白为什么简单地把这个操作放在嵌套的for循环中会大大降低性能?如果有人对如何解决这个问题有任何想法,我将不胜感激!
为什么多处理会减慢python中嵌套的for循环的速度?
创建流程是一项非常昂贵的系统操作。操作系统必须重新映射大量页面(程序、共享库、数据等(,以便新创建的进程可以访问初始进程的页面。多处理包还使用进程间通信,以便在进程之间共享工作。这也很慢。更不用说所需的最终联接操作了。为了提高效率(即尽可能减少开销(,使用多处理包的Python程序应该共享少量数据并执行昂贵的计算。在您的情况下,您不需要多处理包,因为您只使用Numpy数组(请参阅下文(。
这是一种略有不同的方法,我使用numexpr库来进行更快的线性范数运算。对于单个操作,这种方法将运行时间减少了10倍。
Numexpr使用线程,而不是进程,与进程相比,线程更轻(即更便宜(。Numexpr还使用激进优化来尽可能加快计算表达式的速度(CPython没有这样做(。
我不明白为什么简单地将此操作放在嵌套的for循环中会显著降低性能?
Python的默认实现是CPython,它是一个解释器。口译员通常都很慢(尤其是CPython(。CPython几乎不执行代码优化。如果您想要快速循环,那么您需要将它们编译为本机代码或JIT的替代方案。您可以使用Cython或Numba。这两者可以提供简单的方法来并行化程序。在您的情况下,使用Numba可能是最简单的解决方案。您可以从查看示例程序开始。
Update:如果Numpy的实现是多线程的,那么多处理代码可能会慢得多。事实上,每个进程将在一台具有N个内核的机器上创建N个线程。因此将运行N*N个线程。这种情况被称为过度订阅,并且众所周知效率低下(由于抢占式多任务处理,尤其是上下文切换(。检查这一假设的一种方法是简单地查看创建了多少线程(例如,在Posix系统上使用hwloc工具(,或者简单地监控处理器的使用情况。
这只是我对这个问题的快速更新。我发现,当计算不同高维向量之间的欧几里得距离时,我确实在Anaconda中使用numpy得到了最好的结果。在此基础上使用多处理并没有取得任何显著的改进。
然而,我后来通过一个代码示例找到了最近的Faiss库(https://github.com/QVPR/Patch-NetVLAD)。Faiss(https://anaconda.org/pytorch/faiss-gpu)是一个用于聚类和计算不同向量之间距离的库,可用于计算余弦和欧几里得距离。简单地说,这个库可以实现的速度是巨大的,远远超过了比较大量高维矩阵的速度的100倍。它彻底改变了我的研究游戏规则,我强烈推荐它,尤其是在比较大型神经网络描述符时。