Согласно 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. Конечно, вы можете сделать то же самое в Нумбе, но это будет больше работы.