Python: итерация многомерного массива супер медленная?

Я должен перебрать все элементы в двумерном массиве целых чисел и изменить значение (по какому-то правилу это не важно).

Я удивлен, насколько значительна разница в производительности между средой исполнения Python и средой C# или Java. Я написал совершенно неправильный код Python (v2.7.2)?

import numpy
a = numpy.ndarray((5000,5000), dtype = numpy.int32)
for x in numpy.nditer(a.T):
    x = 123
>python -m timeit -n 2 -r 2 -s "import numpy; a = numpy.ndarray((5000,5000), dtype=numpy.int32)" "for x in numpy.nditer(a.T):" "  x = 123"
2 loops, best of 2: 4.34 sec per loop

Например, код C# выполняет всего 50 мс, то есть Python почти в 100 раз медленнее! (предположим, matrix переменная уже инициализирована)

for (y = 0; y < 5000; y++)
for (x = 0; x < 5000; x++)
    matrix[y][x] = 123;

5 ответов

Решение

Python - гораздо более динамичный язык, чем C или C#. Основная причина, почему цикл такой медленный, заключается в том, что на каждом проходе интерпретатор CPython выполняет дополнительную работу, которая тратит время: в частности, он привязывает имя x со следующим объектом из итератора, затем, когда он оценивает присвоение, он должен искать имя x снова.

Как заметил @Sven Marnach, вы можете вызывать функцию метода numpy.fill() и это быстро. Эта функция скомпилирована в C или, может быть, в Fortran, и она просто зациклится на адресах numpy.array Структура данных и заполните значения. Гораздо менее динамичный, чем Python, что хорошо для этого простого случая.

Но теперь рассмотрим PyPy. Когда вы запускаете свою программу под PyPy, JIT анализирует, что на самом деле делает ваш код. В этом примере он отмечает, что имя x не используется ни для чего, кроме назначения, и может оптимизировать связывание имени. Этот пример должен быть очень быстрым для PyPy; скорее всего, PyPy будет в десять раз быстрее обычного Python (так что он будет на одну десятую быстрее C, а не на 1/100).

http://pypy.org/

Насколько я понимаю, PyPy еще некоторое время не будет работать с Numpy, поэтому вы не можете просто запустить существующий код Numpy под PyPy. Но этот день наступает.

Я в восторге от PyPy. Это вселяет надежду, что мы можем писать на языке очень высокого уровня (Python), и в то же время получить почти производительность написания вещей на "переносимом языке ассемблера" (C). Для примеров, подобных этому, Numpy может даже побить производительность наивного кода C, используя SIMD-инструкции от ЦП (SSE2, NEON или что-то еще). В этом примере с SIMD вы можете установить четыре целых числа в 123 для каждого цикла, и это будет быстрее, чем простой цикл C. (Если только компилятор C не использовал SIMD-оптимизацию также! Что, если подумать, скорее всего, подходит для этого случая. Поэтому мы вернулись к "почти скорости C", а не к более быстрому для этого примера. Но мы можем представить более сложные случаи что компилятор C не достаточно умен, чтобы оптимизировать, как мог бы будущий PyPy.)

Но не говоря уже о PyPy. Если вы будете работать с Numpy, это хорошая идея, чтобы изучить все функции, такие как numpy.fill() которые существуют, чтобы ускорить ваш код.

Ага! Итерация по пустым массивам в python медленная. (Медленнее, чем итерация по списку питонов, а также.)

Как правило, вы избегаете итераций по ним напрямую.

Если вы можете дать нам пример правила, на котором вы меняете вещи, есть хороший шанс, что его легко векторизовать.

В качестве примера игрушки:

import numpy as np

x = np.linspace(0, 8*np.pi, 100)
y = np.cos(x)

x[y > 0] = 100

Однако во многих случаях вам приходится выполнять итерации, либо из-за алгоритма (например, методы конечных разностей), либо из-за уменьшения стоимости памяти временных массивов.

В этом случае взгляните на Cython, Weave или что-то подобное.

Пример, который вы привели, предположительно предназначался для установки всех элементов двумерного массива NumPy в значение 123. Это можно эффективно сделать так:

a.fill(123)

или же

a[:] = 123

C++ подчеркивает машинное время над программистским временем.

Python подчеркивает время программиста над временем машины.

Pypy - это питон, написанный на python, и у них есть начало numpy; Вы можете попробовать это. Pypy имеет хороший JIT, который делает вещи довольно быстро.

Вы также можете попробовать Cython, который позволяет вам переводить диалект Python на C и компилировать C в модуль расширения Python C; это позволяет продолжать использовать CPython для большей части вашего кода, в то же время получая некоторое ускорение. Однако в одном микробенчмарке, который я пытался сравнить Pypy и Cython, Pypy был немного быстрее, чем Cython.

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

Если вам нужно выполнить операции над многомерным массивом, которые зависят от значения массива, но не зависят от позиции внутри массива, тогда .itemset в 5 раз быстрее nditer для меня.

Так что вместо того, чтобы делать что-то вроде

image = np.random.random_sample((200, 200,3));
with np.nditer(image, op_flags=['readwrite']) as it:
    for x in it:
        x[...] = x*4.5 if x<0.2 else x

Вы можете сделать это

image2 = np.random.random_sample((200, 200,3));
for i in range(0,image2.size):
    x = image2.item(i)
    image2.itemset(i, x*4.5 if x<0.2 else x);

nditer код не присваивает значение элементам a, Это не влияет на проблему времени, но я упоминаю об этом, потому что это не следует воспринимать как хорошее использование nditer,

правильная версия:

for i in np.nditer(a, op_flags=[["readwrite"]]):
    i[...] = 123

[...] необходим для сохранения ссылки на значение цикла, которое является массивом формы (),

Там нет смысла в использовании A.T, так как его значения базы A что изменилось.

Я согласен, что правильный способ сделать это назначение a[:]=123,

Другие вопросы по тегам