Почему здесь происходит циклическое индексирование битов? - PullRequest
10 голосов
/ 19 августа 2010

Несколько лет назад кто-то отправил на Активные рецепты состояний для сравнения, три функции 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, или кто не смотрел на код, это сравнение не основано на крайнем случае - мне, конечно, не было бы так интересно, если бы оно было. Вместо этого это сравнение включает в себя функцию, которая выполняет общую задачу в матричном вычислении (то есть, создание массива результатов с учетом двух предшественников); кроме того, каждая функция, в свою очередь, состоит из наиболее распространенных встроенных встроенных функций.

Ответы [ 2 ]

11 голосов
/ 19 августа 2010

TL;DR Второй код выше только циклически повторяет количество измерений точек (3 раза по циклу for для 3D-точек), поэтому цикличность невелика.Реальное ускорение во втором приведенном выше коде заключается в том, что он лучше использует возможности Numpy, чтобы избежать создания дополнительных матриц при поиске различий между точками.Это уменьшает используемую память и вычислительные затраты.

Более длинное объяснение Я думаю, что функция calcDistanceMatrixFastEuclidean2 обманывает вас, возможно, своим циклом.Это только цикл по количеству измерений точек.Для точек 1D цикл выполняется только один раз, для 2D - дважды, а для 3D - трижды.Это действительно не слишком много циклов.

Давайте немного разберем код, чтобы понять, почему один работает быстрее, чем другой.calcDistanceMatrixFastEuclidean Я позвоню fast1, а calcDistanceMatrixFastEuclidean2 будет fast2.

fast1 основан на способе действий Matlab, о чем свидетельствует функция repmap.В этом случае функция repmap создает массив, представляющий собой просто исходные данные, повторяемые снова и снова.Однако, если вы посмотрите на код функции, он очень неэффективен.Для этого используется множество функций Numpy (3 reshape с и 2 repeat с).Функция repeat также используется для создания массива, который содержит исходные данные, каждый из которых повторяется много раз.Если наши входные данные [1,2,3], то мы вычитаем [1,2,3,1,2,3,1,2,3] из [1,1,1,2,2,2,3,3,3].Numpy пришлось создать много дополнительных матриц между выполнением кода C Numpy, чего можно было бы избежать.

fast2 использует больше тяжелой работы Numpy, не создавая столько матриц между вызовами Numpy.fast2 перебирает каждое измерение точек, выполняет вычитание и сохраняет промежуточную сумму квадратов разностей между каждым измерением.Только в конце делается квадратный корень.Пока что это может звучать не так эффективно, как fast1, но fast2 избегает делать вещи repmat, используя индексирование Numpy.Давайте посмотрим на 1D случай для простоты.fast2 создает одномерный массив данных и вычитает его из двумерного (N x 1) массива данных.Это создает матрицу различий между каждой точкой и всеми другими точками без необходимости использования repmat и repeat и, таким образом, обходит создание большого количества дополнительных массивов.В этом, на мой взгляд, и есть разница в скорости.fast1 создает много дополнительных между матрицами (и они создаются дорого в вычислительном отношении), чтобы найти различия между точками, в то время как fast2 лучше использует силу Numpy, чтобы избежать их.

Кстати, вот немного более быстрая версия fast2:

def calcDistanceMatrixFastEuclidean3(nDimPoints):
  nDimPoints = array(nDimPoints)
  n,m = nDimPoints.shape
  data = nDimPoints[:,0]
  delta = (data - data[:,newaxis])**2
  for d in xrange(1,m):
    data = nDimPoints[:,d]
    delta += (data - data[:,newaxis])**2
  return sqrt(delta)

Разница в том, что мы больше не создаем дельту как матрицу нулей.

1 голос
/ 19 августа 2010

dis для развлечения:

dis.dis(calcDistanceMatrixFastEuclidean)

  2           0 LOAD_GLOBAL              0 (len)
              3 LOAD_FAST                0 (points)
              6 CALL_FUNCTION            1
              9 STORE_FAST               1 (numPoints)

  3          12 LOAD_GLOBAL              1 (sqrt)
             15 LOAD_GLOBAL              2 (sum)
             18 LOAD_GLOBAL              3 (repmat)
             21 LOAD_FAST                0 (points)
             24 LOAD_FAST                1 (numPoints)
             27 LOAD_CONST               1 (1)
             30 CALL_FUNCTION            3

  4          33 LOAD_GLOBAL              4 (repeat)
             36 LOAD_FAST                0 (points)
             39 LOAD_FAST                1 (numPoints)
             42 LOAD_CONST               2 ('axis')
             45 LOAD_CONST               3 (0)
             48 CALL_FUNCTION          258
             51 BINARY_SUBTRACT
             52 LOAD_CONST               4 (2)
             55 BINARY_POWER
             56 LOAD_CONST               2 ('axis')
             59 LOAD_CONST               1 (1)
             62 CALL_FUNCTION          257
             65 CALL_FUNCTION            1
             68 STORE_FAST               2 (distMat)

  5          71 LOAD_FAST                2 (distMat)
             74 LOAD_ATTR                5 (reshape)
             77 LOAD_FAST                1 (numPoints)
             80 LOAD_FAST                1 (numPoints)
             83 BUILD_TUPLE              2
             86 CALL_FUNCTION            1
             89 RETURN_VALUE

dis.dis(calcDistanceMatrixFastEuclidean2)

  2           0 LOAD_GLOBAL              0 (array)
              3 LOAD_FAST                0 (nDimPoints)
              6 CALL_FUNCTION            1
              9 STORE_FAST               0 (nDimPoints)

  3          12 LOAD_FAST                0 (nDimPoints)
             15 LOAD_ATTR                1 (shape)
             18 UNPACK_SEQUENCE          2
             21 STORE_FAST               1 (n)
             24 STORE_FAST               2 (m)

  4          27 LOAD_GLOBAL              2 (zeros)
             30 LOAD_FAST                1 (n)
             33 LOAD_FAST                1 (n)
             36 BUILD_TUPLE              2
             39 LOAD_CONST               1 ('d')
             42 CALL_FUNCTION            2
             45 STORE_FAST               3 (delta)

  5          48 SETUP_LOOP              76 (to 127)
             51 LOAD_GLOBAL              3 (xrange)
             54 LOAD_FAST                2 (m)
             57 CALL_FUNCTION            1
             60 GET_ITER
        >>   61 FOR_ITER                62 (to 126)
             64 STORE_FAST               4 (d)

  6          67 LOAD_FAST                0 (nDimPoints)
             70 LOAD_CONST               0 (None)
             73 LOAD_CONST               0 (None)
             76 BUILD_SLICE              2
             79 LOAD_FAST                4 (d)
             82 BUILD_TUPLE              2
             85 BINARY_SUBSCR
             86 STORE_FAST               5 (data)

  7          89 LOAD_FAST                3 (delta)
             92 LOAD_FAST                5 (data)
             95 LOAD_FAST                5 (data)
             98 LOAD_CONST               0 (None)
            101 LOAD_CONST               0 (None)
            104 BUILD_SLICE              2
            107 LOAD_GLOBAL              4 (newaxis)
            110 BUILD_TUPLE              2
            113 BINARY_SUBSCR
            114 BINARY_SUBTRACT
            115 LOAD_CONST               2 (2)
            118 BINARY_POWER
            119 INPLACE_ADD
            120 STORE_FAST               3 (delta)
            123 JUMP_ABSOLUTE           61
        >>  126 POP_BLOCK

  8     >>  127 LOAD_GLOBAL              5 (sqrt)
            130 LOAD_FAST                3 (delta)
            133 CALL_FUNCTION            1
            136 RETURN_VALUE

Я не эксперт по dis, но, похоже, вам нужно больше взглянуть на функции, которые вызывает первая, чтобы узнать, почему они требуют времени.Также есть инструмент для профилирования производительности с Python, cProfile.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...