Несколько лет назад кто-то отправил на Активные рецепты состояний для сравнения, три функции 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 2D точек имеет миллион записей). На первый взгляд эта функция выглядела как код, который я использовал для написания, когда изучал NumPy, и я писал бы код 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)
Один из участников (Кейр Мирле) предложил причину, по которой это могло бы быть правдой:
Причина, по которой я подозреваю, что это будет быстрее,
что он имеет лучшую локальность, полностью завершив вычисление на
относительно небольшой рабочий набор, прежде чем перейти к следующему. Один вкладыши
приходится многократно вытягивать потенциально большой массив MxN в процессор.
Судя по собственным сообщениям этого автора, его замечание является лишь подозрением, и, похоже, что оно не обсуждалось далее.
Есть еще мысли о том, как учесть эти результаты?
В частности, есть ли полезное правило - относительно того, когда выполнять цикл и когда индексировать - которое можно извлечь из этого примера в качестве руководства при написании кода numpy?
Для тех, кто не знаком с NumPy, или кто не смотрел на код, это сравнение не основано на крайнем случае - мне, конечно, не было бы так интересно, если бы оно было. Вместо этого это сравнение включает в себя функцию, которая выполняет общую задачу в матричном вычислении (то есть, создание массива результатов с учетом двух предшественников); кроме того, каждая функция, в свою очередь, состоит из наиболее распространенных встроенных встроенных функций.