Spec-Zone.ru › NumPy 1.10

Итерация по массивам

Объект-итератор nditer, представленный в NumPy 1.6, предоставляет множество гибких способов посещения всех элементов одного или нескольких массивов систематическим образом. На этой странице представлены некоторые основные способы использования объекта для вычислений над массивами в Python, а затем приведен вывод о том, как можно ускорить внутренний цикл в Cython. Поскольку Python-интерфейс nditer является относительно прямым отображением API-интерфейса итератора массива C, эти идеи также помогут при работе с итерацией по массивам из C или C++.

Итерация по одному массиву

Наиболее простая задача, которую можно выполнить с помощью nditer, — посещение каждого элемента массива. Каждый элемент предоставляется по одному с использованием стандартного интерфейса Python-итератора.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> for x in np.nditer(a):
...     print x,
...
0 1 2 3 4 5

Важно помнить, что порядок выбирается для соответствия расположению массива в памяти, а не с использованием стандартного порядка C или Fortran. Это делается для повышения эффективности доступа, отражая идею, что по умолчанию необходимо посетить каждый элемент без учета определенного порядка. Мы можем увидеть это, проитерировавшись по транспонированному массиву по сравнению с созданием копии этой транспонированной матрицы в порядке C.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> for x in np.nditer(a.T):
...     print x,
...
0 1 2 3 4 5
>>> for x in np.nditer(a.T.copy(order='C')):
...     print x,
...
0 3 1 4 2 5

Элементы как a , так и a.T просматриваются в том же порядке, а именно в порядке, в котором они хранятся в памяти, тогда как элементы a.T.copy(order=’C’) посещаются в другом порядке, потому что они были помещены в другое расположение в памяти.

Управление порядком итерации

Иногда важно посещать элементы массива в определенном порядке, независимо от расположения элементов в памяти. Объект nditer предоставляет параметр order для управления этим аспектом итерации. По умолчанию, со свойством, описанным выше, используется order=’K’, чтобы сохранить существующий порядок. Это можно переопределить с order=’C’ для порядка C и order=’F’ для порядка Fortran.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> for x in np.nditer(a, order='F'):
...     print x,
...
0 3 1 4 2 5
>>> for x in np.nditer(a.T, order='C'):
...     print x,
...
0 3 1 4 2 5

Изменение значений массива

По умолчанию nditer обрабатывает входной массив как только для чтения. Для изменения элементов массива необходимо указать режим чтения/записи или только записи. Это контролируется флагами для каждого операнда.

Обычная присваивание в Python просто изменяет ссылку в словаре локальных или глобальных переменных, а не изменяет существующую переменную на месте. Это означает, что простое присваивание x не поместит значение в элемент массива, а вместо этого переключится x со ссылки на элемент массива на ссылку на присвоенное значение. Чтобы фактически изменить элемент массива, x должен быть индексирован с помощью многоточия.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> a
array([[0, 1, 2],
       [3, 4, 5]])
>>> for x in np.nditer(a, op_flags=['readwrite']):
...     x[...] = 2 * x
...
>>> a
array([[ 0,  2,  4],
       [ 6,  8, 10]])

Использование внешнего цикла

Во всех предыдущих примерах элементы a предоставляются итератором по одному, потому что вся логика цикла находится внутри итератора. Хотя это просто и удобно, это не очень эффективно. Лучший подход — перенести одномерный внутренний цикл в ваш код, внешний по отношению к итератору. Таким образом, векторные операции NumPy могут использоваться для больших блоков посещаемых элементов.

nditer будет пытаться предоставить блоки, насколько это возможно, для внутреннего цикла. Принудительно задавая порядок ‘C’ и ‘F’, мы получаем различные размеры внешнего цикла. Этот режим активируется указанием флага итератора.

Обратите внимание, что по умолчанию, сохраняя родной порядок памяти, итератор может предоставить один одномерный блок, тогда как принудительно задавая порядок Fortran, ему приходится предоставлять три блока по два элемента каждый.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> for x in np.nditer(a, flags=['external_loop']):
...     print x,
...
[0 1 2 3 4 5]
>>> for x in np.nditer(a, flags=['external_loop'], order='F'):
...     print x,
...
[0 3] [1 4] [2 5]

Отслеживание индекса или многомерного индекса

Во время итерации вы можете захотеть использовать индекс текущего элемента в вычислении. Например, вы можете посетить элементы массива в порядке памяти, но использовать индекс порядка C, Fortran или многомерный индекс для поиска значений в другом массиве.

Протокол Python-итератора не имеет естественного способа запроса этих дополнительных значений от итератора, поэтому мы вводим альтернативную синтаксическую конструкцию для итерации с помощью nditer. Этот синтаксис явно работает с самим объектом итератора, поэтому его свойства легко доступны во время итерации. С помощью этой конструкции цикла текущее значение доступно путем индексирования итератора, а отслеживаемый индекс — это свойство index или multi_index в зависимости от запрошенного.

К сожалению, Python-интерпретатор печатает значения выражений внутри цикла while во время каждой итерации. Мы изменили вывод в примерах, использующих эту конструкцию цикла, чтобы он был более читабельным.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> it = np.nditer(a, flags=['f_index'])
>>> while not it.finished:
...     print "%d <%d>" % (it[0], it.index),
...     it.iternext()
...
0 <0> 1 <2> 2 <4> 3 <1> 4 <3> 5 <5>
>>> it = np.nditer(a, flags=['multi_index'])
>>> while not it.finished:
...     print "%d <%s>" % (it[0], it.multi_index),
...     it.iternext()
...
0 <(0, 0)> 1 <(0, 1)> 2 <(0, 2)> 3 <(1, 0)> 4 <(1, 1)> 5 <(1, 2)>
>>> it = np.nditer(a, flags=['multi_index'], op_flags=['writeonly'])
>>> while not it.finished:
...     it[0] = it.multi_index[1] - it.multi_index[0]
...     it.iternext()
...
>>> a
array([[ 0,  1,  2],
       [-1,  0,  1]])

Отслеживание индекса или многомерного индекса несовместимо с использованием внешнего цикла, так как требует разного значения индекса для каждого элемента. Если вы попытаетесь объединить эти флаги, объект nditer будет генерировать исключение

Пример

>>> a = np.zeros((2,3))
>>> it = np.nditer(a, flags=['c_index', 'external_loop'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Iterator flag EXTERNAL_LOOP cannot be used if an index or multi-index is being tracked

Буферизация элементов массива

Принудительном задании порядка итерации мы наблюдали, что опция внешнего цикла может предоставлять элементы меньшими блоками, поскольку элементы не могут быть посещены в соответствующем порядке с постоянным шагом. При написании кода C это обычно хорошо, однако в чистом Python-коде это может существенно снизить производительность.

Включив режим буферизации, блоки, предоставляемые итератором внутреннему циклу, могут быть увеличены, существенно уменьшая нагрузку Python-интерпретатора. В примере с принудительным заданием порядка итерации Fortran внутренний цикл получает все элементы сразу при включенной буферизации.

Пример

>>> a = np.arange(6).reshape(2,3)
>>> for x in np.nditer(a, flags=['external_loop'], order='F'):
...     print x,
...
[0 3] [1 4] [2 5]
>>> for x in np.nditer(a, flags=['external_loop','buffered'], order='F'):
...     print x,
...
[0 3 1 4 2 5]

Итерация как определенный тип данных

Иногда необходимо рассматривать массив как тип данных, отличный от того, в котором он хранится. Например, возможно, нужно выполнить все вычисления над 64-битными числами с плавающей запятой, даже если обрабатываемые массивы имеют 32-битный тип с плавающей запятой. За исключением написания кода на низком уровне C, обычно лучше позволить итератору обрабатывать копирование или буферизацию, а не приводить тип данных в внутреннем цикле.

Есть два механизма, которые позволяют это сделать: временные копии и режим буферизации. При использовании временных копий создается копия всего массива с новым типом данных, затем выполняется итерация по копии. Доступ к записи разрешен в режиме, который обновляет исходный массив после завершения всех итераций. Основной недостаток временных копий заключается в том, что временная копия может потреблять большое количество памяти, особенно если тип данных итерации имеет больший размер элемента, чем исходный.

Режим буферизации смягчает проблему с потреблением памяти и более эффективен с точки зрения кэша, чем создание временных копий. За исключением особых случаев, когда весь массив требуется сразу вне итератора, буферизация рекомендуется вместо временного копирования. В NumPy буферизация используется функциями ufuncs и другими функциями для поддержки гибких входных данных с минимальными затратами памяти.

В наших примерах мы будем рассматривать входной массив с комплексным типом данных, чтобы мы могли извлекать квадратные корни из отрицательных чисел. Без включения режима копирования или буферизации итератор сгенерирует исключение, если тип данных не совпадает точно.

Пример

>>> a = np.arange(6).reshape(2,3) - 3
>>> for x in np.nditer(a, op_dtypes=['complex128']):
...     print np.sqrt(x),
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Iterator operand required copying or buffering, but neither copying nor buffering was enabled

В режиме копирования ‘copy’ указывается как флаг для каждого операнда. Это делается для управления на уровне каждого операнда. Режим буферизации указывается как флаг итератора.

Пример

>>> a = np.arange(6).reshape(2,3) - 3
>>> for x in np.nditer(a, op_flags=['readonly','copy'],
...                 op_dtypes=['complex128']):
...     print np.sqrt(x),
...
1.73205080757j 1.41421356237j 1j 0j (1+0j) (1.41421356237+0j)
>>> for x in np.nditer(a, flags=['buffered'], op_dtypes=['complex128']):
...     print np.sqrt(x),
...
1.73205080757j 1.41421356237j 1j 0j (1+0j) (1.41421356237+0j)

Итератор использует правила приведения типов NumPy для определения того, разрешено ли конкретное преобразование. По умолчанию он применяет «безопасное» приведение. Это означает, например, что он сгенерирует исключение, если вы попытаетесь обработать массив 64-битных чисел с плавающей запятой как массив 32-битных чисел с плавающей запятой. Во многих случаях правило «same_kind» является наиболее разумным, поскольку оно позволит преобразование из 64-битного в 32-битный тип с плавающей запятой, но не из типа с плавающей запятой в целочисленный или из комплексного в число с плавающей запятой.

Пример

>>> a = np.arange(6.)
>>> for x in np.nditer(a, flags=['buffered'], op_dtypes=['float32']):
...     print x,
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Iterator operand 0 dtype could not be cast from dtype('float64') to dtype('float32') according to the rule 'safe'
>>> for x in np.nditer(a, flags=['buffered'], op_dtypes=['float32'],
...                 casting='same_kind'):
...     print x,
...
0.0 1.0 2.0 3.0 4.0 5.0
>>> for x in np.nditer(a, flags=['buffered'], op_dtypes=['int32'], casting='same_kind'):
...     print x,
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Iterator operand 0 dtype could not be cast from dtype('float64') to dtype('int32') according to the rule 'same_kind'

Следует учитывать преобразования обратно к исходному типу данных при использовании операнда в режиме чтения/записи или только записи. Распространенный случай — реализация внутреннего цикла с использованием 64-битных чисел с плавающей запятой и использование приведения «same_kind» для обработки других типов с плавающей запятой. В режиме только чтения целочисленный массив может быть предоставлен, но в режиме чтения/записи будет сгенерировано исключение, поскольку обратное преобразование к массиву нарушит правило приведения.

Пример

>>> a = np.arange(6)
>>> for x in np.nditer(a, flags=['buffered'], op_flags=['readwrite'],
...                 op_dtypes=['float64'], casting='same_kind'):
...     x[...] = x / 2.0
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
TypeError: Iterator requested dtype could not be cast from dtype('float64') to dtype('int64'), the operand 0 dtype, according to the rule 'same_kind'

Итерация с вещанием массивов

NumPy имеет набор правил для обработки массивов с различными формами, которые применяются всякий раз, когда функции принимают несколько операндов, которые комбинируются по элементам. Это называется вещанием. Объект nditer может применять эти правила за вас, когда вам нужно написать такую функцию.

В качестве примера выведем результат вещания одномерного и двумерного массива.

Пример

>>> a = np.arange(3)
>>> b = np.arange(6).reshape(2,3)
>>> for x, y in np.nditer([a,b]):
...     print "%d:%d" % (x,y),
...
0:0 1:1 2:2 0:3 1:4 2:5

При возникновении ошибки вещания итератор генерирует исключение, которое включает в себя входные формы, чтобы помочь в диагностике проблемы.

Пример

>>> a = np.arange(2)
>>> b = np.arange(6).reshape(2,3)
>>> for x, y in np.nditer([a,b]):
...     print "%d:%d" % (x,y),
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: operands could not be broadcast together with shapes (2) (2,3)

Итератор-выделенные выходные массивы

В функциях NumPy распространён случай выделения выходных данных на основе вещания ввода, а также наличие необязательного параметра «out», где результат будет помещён при его предоставлении. Объект nditer предоставляет удобный idiom, который значительно упрощает поддержку этого механизма.

Покажем, как это работает, создав функцию square, которая возводит в квадрат свой вход. Начнём с минимального определения функции без поддержки параметра ‘out’.

Пример

>>> def square(a):
...     it = np.nditer([a, None])
...     for x, y in it:
...          y[...] = x*x
...     return it.operands[1]
...
>>> square([1,2,3])
array([1, 4, 9])

По умолчанию, nditer использует флаги ‘allocate’ и ‘writeonly’ для операндов, переданных как None. Это означает, что мы смогли предоставить всего два операнда итератору, а он обработал остальное.

При добавлении параметра ‘out’ мы должны явно указать эти флаги, потому что если кто-то передаёт массив как ‘out’, итератор по умолчанию установит флаг ‘readonly’, и наш внутренний цикл завершится с ошибкой. Причиной, по которой ‘readonly’ является значением по умолчанию для входных массивов, является предотвращение случайного запуска операции сокращения. Если значением по умолчанию было бы ‘readwrite’, любая операция расширения также запускала бы сокращение, тема, которая рассматривается позже в этом документе.

Пока мы тут, давайте также представим флаг ‘no_broadcast’, который предотвратит распространение вывода. Это важно, потому что мы хотим получить только одно входное значение для каждого выходного. Агрегация более одного входного значения — это операция сокращения, которая требует специальной обработки. Она уже вызовет ошибку, потому что сокращения должны быть явно включены в флаге итератора, но сообщение об ошибке, которое возникает из-за отключения распространения, намного понятнее для конечных пользователей. Чтобы узнать, как обобщить функцию возведения в квадрат на сокращение, посмотрите на функцию суммы квадратов в разделе про Cython.

Для полноты, мы также добавим флаги ‘external_loop’ и ‘buffered’, так как они обычно необходимы по соображениям производительности.

Пример

>>> def square(a, out=None):
...     it = np.nditer([a, out],
...             flags = ['external_loop', 'buffered'],
...             op_flags = [['readonly'],
...                         ['writeonly', 'allocate', 'no_broadcast']])
...     for x, y in it:
...         y[...] = x*x
...     return it.operands[1]
...
>>> square([1,2,3])
array([1, 4, 9])
>>> b = np.zeros((3,))
>>> square([1,2,3], out=b)
array([ 1.,  4.,  9.])
>>> b
array([ 1.,  4.,  9.])
>>> square(np.arange(6).reshape(2,3), out=b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in square
ValueError: non-broadcastable output operand with shape (3) doesn't match the broadcast shape (2,3)

Итерация внешнего произведения

Любая бинарная операция может быть расширена до операции над массивом в формате внешнего произведения, как в outer, и объект nditer предоставляет способ достижения этого путём явного сопоставления осей операндов. Это также можно сделать с помощью индексирования newaxis, но мы покажем вам, как напрямую использовать параметр nditer op_axes для достижения этого без промежуточных представлений.

Мы выполним простое внешнее произведение, разместив измерения первого операнда перед измерениями второго операнда. Параметр op_axes требует одного списка осей для каждого операнда и предоставляет отображение осей итератора на оси операнда.

Предположим, что первый операнд одномерный, а второй — двумерный. Итератор будет иметь три измерения, поэтому op_axes будет содержать два списка по 3 элемента. Первый список выбирает одну ось первого операнда, и содержит -1 для остальных осей итератора, в результате получая [0, -1, -1]. Второй список выбирает две оси второго операнда, но не должен перекрываться с осями, выбранными для первого операнда. Его список — [-1, 0, 1]. Операнд вывода отображается на оси итератора стандартным образом, поэтому мы можем передать None вместо создания другого списка.

Операция во внутреннем цикле — простое умножение. Все, что связано с внешним произведением, обрабатывается настройкой итератора.

Пример

>>> a = np.arange(3)
>>> b = np.arange(8).reshape(2,4)
>>> it = np.nditer([a, b, None], flags=['external_loop'],
...             op_axes=[[0, -1, -1], [-1, 0, 1], None])
>>> for x, y, z in it:
...     z[...] = x*y
...
>>> it.operands[2]
array([[[ 0,  0,  0,  0],
        [ 0,  0,  0,  0]],
       [[ 0,  1,  2,  3],
        [ 4,  5,  6,  7]],
       [[ 0,  2,  4,  6],
        [ 8, 10, 12, 14]]])

Итерация сокращения

Всякий раз, когда у записываемого операнда меньше элементов, чем весь пространство итерации, этот операнд подвергается сокращению. Объект nditer требует, чтобы любой операнд сокращения был помечен как читаемо-записываемый, и допускает сокращения только при наличии флага итератора ‘reduce_ok’.

Для простого примера рассмотрим вычисление суммы всех элементов в массиве.

Пример

>>> a = np.arange(24).reshape(2,3,4)
>>> b = np.array(0)
>>> for x, y in np.nditer([a, b], flags=['reduce_ok', 'external_loop'],
...                     op_flags=[['readonly'], ['readwrite']]):
...     y[...] += x
...
>>> b
array(276)
>>> np.sum(a)
276

Когда дело доходит до сочетания сокращения и выделенных операндов, всё немного сложнее. Перед началом итерации любой операнд сокращения должен быть инициализирован своими начальными значениями. Вот как мы можем это сделать, вычисляя суммы по последней оси a.

Пример

>>> a = np.arange(24).reshape(2,3,4)
>>> it = np.nditer([a, None], flags=['reduce_ok', 'external_loop'],
...             op_flags=[['readonly'], ['readwrite', 'allocate']],
...             op_axes=[None, [0,1,-1]])
>>> it.operands[1][...] = 0
>>> for x, y in it:
...     y[...] += x
...
>>> it.operands[1]
array([[ 6, 22, 38],
       [54, 70, 86]])
>>> np.sum(a, axis=2)
array([[ 6, 22, 38],
       [54, 70, 86]])

Для буферизованного сокращения требуется ещё одно изменение во время настройки. Обычно создание итератора включает копирование первого буфера данных из читаемых массивов в буфер. Любой операнд сокращения читается, поэтому он может быть помещён в буфер. К сожалению, инициализация операнда после завершения этой операции буферизации не будет отражена в буфере, с которым начинается итерация, и будут получены неверные результаты.

Флаг итератора “delay_bufalloc” предназначен для того, чтобы итератор выделенных операндов сокращения могли существовать вместе с буферизацией. При установке этого флага итератор оставит свои буферы неинициализированными до получения сброса, после чего он будет готов к обычной итерации. Вот как предыдущий пример выглядит, если мы также включим буферизацию.

Пример

>>> a = np.arange(24).reshape(2,3,4)
>>> it = np.nditer([a, None], flags=['reduce_ok', 'external_loop',
...                                  'buffered', 'delay_bufalloc'],
...             op_flags=[['readonly'], ['readwrite', 'allocate']],
...             op_axes=[None, [0,1,-1]])
>>> it.operands[1][...] = 0
>>> it.reset()
>>> for x, y in it:
...     y[...] += x
...
>>> it.operands[1]
array([[ 6, 22, 38],
       [54, 70, 86]])

Встраивание внутреннего цикла в Cython

Те, кто хочет действительно высокой производительности своих низкоуровневых операций, должны серьёзно рассмотреть прямой использование API итераций, предоставленного на C, но для тех, кто не знаком с C или C++, Cython является хорошим средством с приемлемыми компромиссами в производительности. Для объекта nditer это означает предоставление итератору заботы о расширении, преобразовании типов и буферизации, в то время как внутренний цикл передаётся Cython.

Для нашего примера мы создадим функцию суммы квадратов. Начнём с реализации этой функции в обычном Python. Мы хотим поддерживать параметр ‘axis’, подобный функции numpy sum, поэтому нам понадобится создать список для параметра op_axes. Вот как это выглядит.

Пример

>>> def axis_to_axeslist(axis, ndim):
...     if axis is None:
...         return [-1] * ndim
...     else:
...         if type(axis) is not tuple:
...             axis = (axis,)
...         axeslist = [1] * ndim
...         for i in axis:
...             axeslist[i] = -1
...         ax = 0
...         for i in range(ndim):
...             if axeslist[i] != -1:
...                 axeslist[i] = ax
...                 ax += 1
...         return axeslist
...
>>> def sum_squares_py(arr, axis=None, out=None):
...     axeslist = axis_to_axeslist(axis, arr.ndim)
...     it = np.nditer([arr, out], flags=['reduce_ok', 'external_loop',
...                                       'buffered', 'delay_bufalloc'],
...                 op_flags=[['readonly'], ['readwrite', 'allocate']],
...                 op_axes=[None, axeslist],
...                 op_dtypes=['float64', 'float64'])
...     it.operands[1][...] = 0
...     it.reset()
...     for x, y in it:
...         y[...] += x*x
...     return it.operands[1]
...
>>> a = np.arange(6).reshape(2,3)
>>> sum_squares_py(a)
array(55.0)
>>> sum_squares_py(a, axis=-1)
array([  5.,  50.])

Чтобы переписать эту функцию в Cython, мы заменяем внутренний цикл (y[...] += x*x) кодом Cython, специализированным для типа данных float64. С включенным флагом ‘external_loop’, массивы, передаваемые во внутренний цикл, всегда будут одномерными, поэтому требуется очень мало проверок.

Вот список sum_squares.pyx:

import numpy as np
cimport numpy as np
cimport cython

def axis_to_axeslist(axis, ndim):
    if axis is None:
        return [-1] * ndim
    else:
        if type(axis) is not tuple:
            axis = (axis,)
        axeslist = [1] * ndim
        for i in axis:
            axeslist[i] = -1
        ax = 0
        for i in range(ndim):
            if axeslist[i] != -1:
                axeslist[i] = ax
                ax += 1
        return axeslist

@cython.boundscheck(False)
def sum_squares_cy(arr, axis=None, out=None):
    cdef np.ndarray[double] x
    cdef np.ndarray[double] y
    cdef int size
    cdef double value

    axeslist = axis_to_axeslist(axis, arr.ndim)
    it = np.nditer([arr, out], flags=['reduce_ok', 'external_loop',
                                      'buffered', 'delay_bufalloc'],
                op_flags=[['readonly'], ['readwrite', 'allocate']],
                op_axes=[None, axeslist],
                op_dtypes=['float64', 'float64'])
    it.operands[1][...] = 0
    it.reset()
    for xarr, yarr in it:
        x = xarr
        y = yarr
        size = x.shape[0]
        for i in range(size):
           value = x[i]
           y[i] = y[i] + value * value
    return it.operands[1]

На этом компьютере построение файла .pyx в модуль выглядело так, но вам может потребоваться найти руководства по Cython, чтобы узнать детали для вашей конфигурации системы.

$ cython sum_squares.pyx
$ gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -I/usr/include/python2.7 -fno-strict-aliasing -o sum_squares.so sum_squares.c

Запуск этого из интерпретатора Python даёт те же ответы, что и наш исходный код Python/NumPy.

Пример

>>> from sum_squares import sum_squares_cy
>>> a = np.arange(6).reshape(2,3)
>>> sum_squares_cy(a)
array(55.0)
>>> sum_squares_cy(a, axis=-1)
array([  5.,  50.])

Небольшое измерение времени в IPython показывает, что сокращённая накладная стоимость и выделение памяти внутреннего цикла Cython обеспечивают значительный прирост скорости по сравнению как со стандартным кодом Python, так и с выражением, использующим встроенную функцию суммирования NumPy.

>>> a = np.random.rand(1000,1000)

>>> timeit sum_squares_py(a, axis=-1)
10 loops, best of 3: 37.1 ms per loop

>>> timeit np.sum(a*a, axis=-1)
10 loops, best of 3: 20.9 ms per loop

>>> timeit sum_squares_cy(a, axis=-1)
100 loops, best of 3: 11.8 ms per loop

>>> np.all(sum_squares_cy(a, axis=-1) == np.sum(a*a, axis=-1))
True

>>> np.all(sum_squares_py(a, axis=-1) == np.sum(a*a, axis=-1))
True

© 2008–2016 NumPy Developers
Licensed under the NumPy License.
https://docs.scipy.org/doc/numpy-1.10.1/reference/arrays.nditer.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API