NumPy: альтернатива `векторизации`, которая позволяет мне получить доступ к массиву - PullRequest
6 голосов
/ 10 июля 2020

У меня есть этот код:

output_array = np.vectorize(f, otypes='d')(input_array)

И я хотел бы заменить его этим кодом, который должен давать тот же результат:

output_array = np.ndarray(input_array.shape, dtype='d')
for i, item in enumerate(input_array):
    output_array[i] = f(item)

Причина, по которой я хочу, чтобы вторая версия заключалась в том, чтобы затем я мог начать повторение output_array в отдельном потоке, пока он вычисляется. (Да, я знаю о GIL, об этом уже позаботились.)

К сожалению, for l oop работает очень медленно, даже когда я не обрабатываю данные в отдельном потоке. Я протестировал его как на CPython, так и на PyPy3, что является моей целевой платформой. На CPython он в 3 раза медленнее, чем vectorize, а на PyPy3 он в 67 раз медленнее, чем vectorize!

Это несмотря на то, что в документации Numpy написано: «vectorize Функция предоставляется в первую очередь для удобства, а не для производительности. Реализация по существу является для l oop. "

Любая идея, почему моя реализация медленная, и как сделать быструю реализацию, которая все еще позволяет мне использовать output_array до завершения?

Ответы [ 2 ]

1 голос
/ 12 июля 2020

Себастьян Берг дал мне решение. При переборе элементов из входного массива используйте item.item(), а не просто item. Это превращает объекты numpy.float64 в обычные Python плавающие объекты, делая все намного быстрее и решая мою конкретную проблему:)

0 голосов
/ 10 июля 2020

Векторизация в numpy обеспечит повышение производительности численных вычислений по сравнению с l oop, потому что это сокращает некоторые накладные расходы интерпретатора Python на динамическое определение некоторых характеристик данных (например, его тип и местоположение) во время выполнения.

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

Для n-мерных данных по умолчанию numpy сохраняет значения в C упорядочении (основная строка) , так что последовательные элементы строки хранятся рядом друг с другом.

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

Когда вы используете векторизованную функцию для объекта ndarray, он ведет себя m или вроде C код, чем как Python. Другими словами, он работает непосредственно со значениями в памяти и изменяет их, а также пользуется преимуществами всех оптимизаций хранения и типов.

Я подозреваю, что когда вы не векторизуете свою функцию f, вы сильно увеличиваете накладные расходы интерпретатора Python. При успешной векторизации большая часть этой функции будет выполняться в C и избежать большей части замедления, которое вносит Python. Я задавался вопросом, не смог ли enumerate воспользоваться преимуществами базовой структуры данных так, как объект numpy nditer, но я протестировал nditer, numpy ufuncs и явный цикл for с enumerate и enumerate на самом деле был самым быстрым итератором, поэтому я предполагаю, что ваша пользовательская функция, вероятно, является виновником времени. Это имеет смысл, особенно с учетом того, что PyPy имеет намного более драматическое c замедление.

Примеры тестов:

a = np.ndarray()

>>> %%time
>>> for x in np.nditer(a, flags=['external_loop']):
>>> ....    x*x
CPU times: user 201 ms, sys: 219 ms, total: 420 ms
Wall time: 420 ms

>>> %%time
>>> np.square(a)
CPU times: user 201 ms, sys: 180 ms, total: 381 ms
Wall time: 380 ms

>>> %%time
>>> for i, x in enumerate(a):
>>> ....    x*x

CPU times: user 78.5 ms, sys: 1.79 ms, total: 80.3 ms
Wall time: 79.4 ms
...