几年前,有人在Active State Recipes上使用了三个python/NumPy函数,每个函数接受相同的参数并返回相同的结果,即距离矩阵。在
其中两个是从已发布的源代码中获取的;它们都是——或者在我看来是——惯用的numpy代码。创建距离矩阵所需的重复计算由numpy优雅的索引语法驱动。以下是其中之一:
from numpy.matlib import repmat, repeat
def calcDistanceMatrixFastEuclidean(points):
numPoints = len(points)
distMat = sqrt(sum((repmat(points, numPoints, 1) -
repeat(points, numPoints, axis=0))**2, axis=1))
return distMat.reshape((numPoints,numPoints))
第三个使用一个循环创建了距离矩阵(显然,这是一个很大的循环,因为距离矩阵只有1000个二维点,有一百万个条目)。乍一看,这个函数看起来像我学习NumPy时编写的代码,我会先编写Python代码,然后逐行翻译,来编写NumPy代码。在
在activestate发布几个月后,比较这三者的性能测试结果发布在NumPy邮件列表上的thread中并进行了讨论。在
实际上,带有循环的函数的性能明显优于其他两个函数:
^{pr2}$线程中的一位参与者(Keir Mierle)给出了一个可能是真的原因:
The reason that I suspect this will be faster is that it has better locality, completely finishing a computation on a relatively small working set before moving onto the next one. The one liners have to pull the potentially large MxN array into the processor repeatedly.
据这名发帖人自己说,他的话只是一种怀疑,似乎没有进一步讨论过。在
关于如何解释这些结果还有什么想法吗?在
特别是,是否有一个有用的规则——关于何时循环和何时索引——可以从这个例子中提取出来作为编写numpy代码的指导?在
对于那些不熟悉NumPy的人,或者没有看过代码的人来说,这种比较并不是基于边缘情况——如果是的话,我肯定不会那么感兴趣。相反,这种比较涉及到一个函数,该函数在矩阵计算中执行一个公共任务(即,创建一个给定两个前件的结果数组);此外,每个函数依次由最常见的numpy内置函数组成。在
TL;DR上面的第二个代码只在点的维数上循环(对于3D点,循环次数是for循环的3倍),因此循环次数不多。上面第二个代码的真正加速是它更好地利用Numpy的能力,以避免在找到点之间的差异时创建一些额外的矩阵。这减少了内存使用和计算工作量。在
更长的解释 我认为
calcDistanceMatrixFastEuclidean2
函数的循环可能在欺骗你。它只是在点的维数上循环。对于1D点,循环只执行一次,对于2D,执行两次,对于3D,执行三次。这真的不是什么循环。在让我们分析一下代码,看看为什么其中一个比另一个快。}将是{}。在
calcDistanceMatrixFastEuclidean
我将调用fast1
,而{fast1
是基于Matlab的做事方式的,repmap
函数就证明了这一点。在本例中,repmap
函数创建了一个数组,它只是反复重复的原始数据。但是,如果查看函数的代码,则效率非常低。它使用许多Numpy函数(3reshape
s和2repeat
s)来实现这一点。repeat
函数还用于创建一个数组,该数组包含每个数据项重复多次的原始数据。如果我们的输入数据是[1,2,3]
,那么我们将从[1,1,1,2,2,2,3,3,3]
中减去{fast2
使用了更多Numpy的重担,而没有在Numpy调用之间创建那么多的矩阵。fast2
遍历点的每个维度,进行减法,并保持每个维度之间的平方差的累计。只有到最后才算平方根。到目前为止,这听起来不像fast1
那么有效,但是fast2
通过使用Numpy的索引避免了repmat
的工作。为了简单起见,让我们看一下1D案例。fast2
生成数据的1D数组,并从2D(nx1)数组中减去它。这将在每个点和所有其他点之间创建差分矩阵,而不必使用repmat
和repeat
,从而避免了创建大量额外的数组。这就是我认为真正的速度差异所在。fast1
在矩阵之间创建了很多额外的元素(它们的创建是非常昂贵的计算)来发现点之间的差异,而{{cd5>稍微快一点
不同的是,我们不再创建delta作为零矩阵。在
dis
为了好玩:dis.dis(calcDistanceMatrixFastEuclidean)
^{pr2}$dis.dis(calcDistanceMatrixFastEuclidean2)
我不是
dis
方面的专家,但似乎您需要更多地研究第一个调用的函数,以了解它们为什么要花费一段时间。Python还有一个性能分析器工具,cProfile
。在相关问题 更多 >
编程相关推荐