几年前,有人在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))
第三个创建了距离矩阵的函数使用了单循环(显然是大量循环,因为一个只有1,000个2D点的距离矩阵就有一百万个条目)。乍一看,这个函数看起来像我学习NumPy时编写的代码,我会先编写Python代码,然后逐行转换。
在Active State发布几个月后,对比这三个函数的性能测试结果被发布并在NumPy邮件列表的线程中讨论。
事实上,带有循环的函数明显优于其他两个函数:
from numpy import mat, zeros, newaxis
def calcDistanceMatrixFastEuclidean2(nDimPoints):
nDimPoints = array(nDimPoints)
n,m = nDimPoints.shape
delta = zeros((n,n),'d')
for d in xrange(m):
data = nDimPoints[:,d]
delta += (data - data[:,newaxis])**2
return sqrt(delta)
一个帖子的参与者(Keir Mierle)提供了一个可能为真实的原因:
我怀疑这样做更快的原因在于它具有更好的局部性,在转移到下一个较小的工作集之前,完全完成对一个相对较小的工作集的计算。一行代码需要重复地将潜在的大型MxN数组拉入处理器。
根据这位发帖者自己的说法,他的评论只是一种怀疑,并且似乎没有进一步讨论。
对于如何解释这些结果,是否有其他想法?
特别是,是否可以从此示例中提取出有用的规则,关于何时循环和何时索引,作为编写NumPy代码的指导?
对于那些不熟悉NumPy或还没有查看过代码的人来说,这种比较并不是基于一种边缘情况--如果是这样,它肯定不会吸引我的注意。相反,这个比较涉及到矩阵计算中的常见任务(即,在给定两个前置条件的情况下创建结果数组),而且每个函数又由最常见的NumPy内置函数组成。