Нумба на чистом питоне VS Нумба на чистом питоне - PullRequest
0 голосов
/ 14 октября 2019

Использование Numba приводит к гораздо более быстрым программам, чем использование чистого Python:

Кажется, к настоящему времени установлено, что numba на чистом питоне даже (в большинстве случаев) быстрее numpy-python, например, https://jakevdp.github.io/blog/2015/02/24/optimizing-python-with-numpy-and-numba/.

Согласно https://murillogroupmsu.com/julia-set-speed-comparison/ numba используется начистый код Python быстрее, чем используется в коде Python, который использует Numpy. Это вообще правда и почему?

В https://stackoverflow.com/a/25952400/4533188 объясняется, почему numba на чистом python быстрее, чем numpy-python: numba видит больше кода и имеет больше способов оптимизировать код, чем numpy, которыйвидит только небольшую часть.

Отвечает ли это на мой вопрос? Не мешаю ли я numba полностью оптимизировать мой код при использовании numpy, потому что numba вынуждена использовать подпрограммы numpy вместо того, чтобы находить еще более оптимальный путь? Я надеялся, что numba поймет это и не будет использовать эти рутинные процедуры, если это не выгодно. Тогда он будет использовать NumPy рутины, только это улучшение (в конце концов NumPy довольно хорошо протестированы). Afterall «Поддержка массивов NumPy является ключевым направлением развития Numba и в настоящее время подвергается обширной рефакторизации и улучшению».

Ответы [ 2 ]

3 голосов
/ 14 октября 2019

Давайте разберемся несколько вещей, прежде чем я отвечу на конкретные вопросы:

  • Я рассмотрю только код nopython для этого ответа, код объектного режима часто медленнее, чем чистые эквиваленты Python / NumPy.
  • Я проигнорирую возможности графического процессора numba для этого ответа - трудно сравнить код, выполняемый на графическом процессоре, с кодом, работающим на процессоре.
  • Когда вы вызываете функцию NumPy в функции numbaвы на самом деле не вызываете функцию NumPy. Все, что поддерживает numba, повторно реализовано в numba. Это относится как к функциям NumPy, так и к типам данных Python в numba! Таким образом, детали реализации между Python / NumPy внутри функции numba и снаружи могут отличаться, потому что это абсолютно разные функции / типы.
  • Numba генерирует код, который скомпилирован с LLVM. Numba не волшебство, это просто оболочка для оптимизирующего компилятора с некоторыми оптимизациями, встроенными в numba!

Кажется, к настоящему времени установлено, что numba на чистом python является четной (в большинстве случаев) быстрее, чем numpy-python

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

numba, используемая в чистом коде Python, быстрее, чем в коде Python, использующем Numpy,Это вообще правда и почему?

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

Do Iмешать numba полностью оптимизировать мой код при использовании numpy, потому что numba вынужден использовать подпрограммы numpy вместо того, чтобы находить еще более оптимальный способ?

Да.

IЯ надеялся, что numba поймет это и не будет использовать пустые процедуры, если это не выгодно.

Нет, в настоящее время numba работает не так. Numba просто создает код для компиляции LLVM. Может быть, это особенность Numba будет в будущем (кто знает). В настоящее время numba работает лучше всего, если вы пишете циклы и операции самостоятельно и избегаете вызова функций NumPy внутри функций numba.

Существует несколько библиотек, которые используют деревья выражений и могут оптимизировать бесполезные вызовы функций NumPy, но обычно этоне допускайте быстрой ручной итерации. Например, numexpr может оптимизировать множественные вызовы функций NumPy. На данный момент это либо быстрая ручная итерация (cython / numba), либо оптимизация связанных вызовов NumPy с использованием деревьев выражений (Numberxpr). Может быть, даже невозможно сделать оба в одной библиотеке - я не знаю.


Numba и Cython великолепны, когда дело доходит до маленьких массивов и быстрой ручной итерации по массивам. NumPy / SciPy великолепны, потому что они оснащены множеством сложных функций для выполнения различных задач из коробки. Numexpr отлично подходит для объединения нескольких вызовов функций NumPy. В некоторых случаях Python работает быстрее, чем любой из этих инструментов.

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

1 голос
/ 14 октября 2019

Согласно https://murillogroupmsu.com/julia-set-speed-comparison/ numba, используемая в чистом коде Python, быстрее, чем в коде Python, использующем Numpy. Это вообще правда и почему?

В https://stackoverflow.com/a/25952400/4533188 объясняется, почему numba на чистом python быстрее, чем numpy-python: numba видит больше кода и имеет больше способов оптимизировать код, чем numpy, которыйвидит только небольшую часть.

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

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

[Edit] Оптимизации Раздел 1.10.4,Диагностику (например, слияние контуров), выполняемую в параллельном ускорителе, можно включить в однопоточном режиме, также установив parallel=True и nb.parfor.sequential_parfor_lowering = True. 1

Пример

#only for single-threaded numpy test
import os
os.environ["OMP_NUM_THREADS"] = "1"

import numba as nb
import numpy as np

a=np.random.rand(100_000_000)
b=np.random.rand(100_000_000)
c=np.random.rand(100_000_000)
d=np.random.rand(100_000_000)

#Numpy version
#every expression is evaluated on its own 
#the summation algorithm (Pairwise summation) isn't equivalent to the algorithm I used below
def Test_np(a,b,c,d):
    return np.sum(a+b*2.+c*3.+d*4.)

#The same code, but for Numba (results and performance differ)
@nb.njit(fastmath=False,parallel=True)
def Test_np_nb(a,b,c,d):
    return np.sum(a+b*2.+c*3.+d*4.)

#the summation isn't fused, aprox. the behaiviour of Test_np_nb for 
#single threaded target
@nb.njit(fastmath=False,parallel=True)
def Test_np_nb_eq(a,b,c,d):
    TMP=np.empty(a.shape[0])
    for i in nb.prange(a.shape[0]):
        TMP[i]=a[i]+b[i]*2.+c[i]*3.+d[i]*4.

    res=0.
    for i in nb.prange(a.shape[0]):
        res+=TMP[i]

    return res

#The usual way someone would implement this in Numba
@nb.njit(fastmath=False,parallel=True)
def Test_nb(a,b,c,d):
    res=0.
    for i in nb.prange(a.shape[0]):
        res+=a[i]+b[i]*2.+c[i]*3.+d[i]*4.
    return res

Время

#single-threaded
%timeit res_1=Test_nb(a,b,c,d)
178 ms ± 1.28 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_2=Test_np(a,b,c,d)
2.72 s ± 118 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_3=Test_np_nb(a,b,c,d)
562 ms ± 5.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_4=Test_np_nb_eq(a,b,c,d)
612 ms ± 6.08 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

#single-threaded
#parallel=True
#nb.parfor.sequential_parfor_lowering = True
%timeit res_1=Test_nb(a,b,c,d)
188 ms ± 5.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit res_3=Test_np_nb(a,b,c,d)
184 ms ± 817 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit res_4=Test_np_nb_eq(a,b,c,d)
185 ms ± 1.14 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

#multi-threaded
%timeit res_1=Test_nb(a,b,c,d)
105 ms ± 3.08 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_2=Test_np(a,b,c,d)
1.78 s ± 75.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_3=Test_np_nb(a,b,c,d)
102 ms ± 686 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit res_4=Test_np_nb_eq(a,b,c,d)
102 ms ± 1.48 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Результаты

#single-threaded
res_1=Test_nb(a,b,c,d)
499977967.27572954
res_2=Test_np(a,b,c,d)
499977967.2756622
res_3=Test_np_nb(a,b,c,d)
499977967.2756614
res_4=Test_np_nb_eq(a,b,c,d)
499977967.2756614

#multi-threaded
res_1=Test_nb(a,b,c,d)
499977967.27572465
res_2=Test_np(a,b,c,d)
499977967.2756622
res_3=Test_np_nb(a,b,c,d)
499977967.27572465
res_4=Test_np_nb_eq(a,b,c,d)
499977967.27572465

Заключение

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

Я также специально использовал здесь пример суммирования. Делать все это одновременно легко и быстро, но если я хочу получить наиболее точный результат, я бы определенно использовал более сложный алгоритм, который уже реализован в Numpy. Конечно, вы можете сделать то же самое в Нумбе, но это будет больше работы.

...