Несколько лет назад кто-то опубликовал в Active State Recipes для целей сравнения, три функции python/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, и я бы написал код NumPy, сначала написав код Python, а затем переведя его, построчно.
Спустя несколько месяцев после публикации Active State результаты тестов производительности, сравнивающих три, были опубликованы и обсуждены в thread в списке рассылки 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.