Dlaczego eMinum numpy jest szybsze niż wbudowane funkcje numpy?
Zacznijmy od trzech tablicdtype=np.double
. Harmonogramy są wykonywane na procesorze Intel przy użyciu skompilowanego programu numpy 1.7.1icc
i związany z intelmkl
. Procesor AMD z skompilowanym numpy 1.6.1gcc
bezmkl
został również wykorzystany do weryfikacji czasu. Proszę zwrócić uwagę, że skale czasowe są skalowane prawie liniowo z wielkością systemu i nie są spowodowane małym nakładem ponoszonym przez funkcje numpyif
stwierdzenia te różnice pojawią się w mikrosekundach, a nie milisekundach:
arr_1D=np.arange(500,dtype=np.double)
large_arr_1D=np.arange(100000,dtype=np.double)
arr_2D=np.arange(500**2,dtype=np.double).reshape(500,500)
arr_3D=np.arange(500**3,dtype=np.double).reshape(500,500,500)
Najpierw spójrzmy nanp.sum
funkcjonować:
np.all(np.sum(arr_3D)==np.einsum('ijk->',arr_3D))
True
%timeit np.sum(arr_3D)
10 loops, best of 3: 142 ms per loop
%timeit np.einsum('ijk->', arr_3D)
10 loops, best of 3: 70.2 ms per loop
Uprawnienie:
np.allclose(arr_3D*arr_3D*arr_3D,np.einsum('ijk,ijk,ijk->ijk',arr_3D,arr_3D,arr_3D))
True
%timeit arr_3D*arr_3D*arr_3D
1 loops, best of 3: 1.32 s per loop
%timeit np.einsum('ijk,ijk,ijk->ijk', arr_3D, arr_3D, arr_3D)
1 loops, best of 3: 694 ms per loop
Produkt zewnętrzny:
np.all(np.outer(arr_1D,arr_1D)==np.einsum('i,k->ik',arr_1D,arr_1D))
True
%timeit np.outer(arr_1D, arr_1D)
1000 loops, best of 3: 411 us per loop
%timeit np.einsum('i,k->ik', arr_1D, arr_1D)
1000 loops, best of 3: 245 us per loop
Wszystkie powyższe są dwa razy szybszenp.einsum
. Powinny to być porównania jabłek z jabłkami, ponieważ wszystko jest specyficznedtype=np.double
. Spodziewałbym się przyspieszenia w takiej operacji:
np.allclose(np.sum(arr_2D*arr_3D),np.einsum('ij,oij->',arr_2D,arr_3D))
True
%timeit np.sum(arr_2D*arr_3D)
1 loops, best of 3: 813 ms per loop
%timeit np.einsum('ij,oij->', arr_2D, arr_3D)
10 loops, best of 3: 85.1 ms per loop
Wydaje się, że Einsum jest co najmniej dwa razy szybszynp.inner
, np.outer
, np.kron
, inp.sum
niezależnie od tegoaxes
wybór. Podstawowym wyjątkiem jestnp.dot
jak wywołuje DGEMM z biblioteki BLAS. Dlaczego tak jestnp.einsum
szybciej niż inne równoważne funkcje?
Sprawa DGEMM dla kompletności:
np.allclose(np.dot(arr_2D,arr_2D),np.einsum('ij,jk',arr_2D,arr_2D))
True
%timeit np.einsum('ij,jk',arr_2D,arr_2D)
10 loops, best of 3: 56.1 ms per loop
%timeit np.dot(arr_2D,arr_2D)
100 loops, best of 3: 5.17 ms per loop
Wiodąca teoria pochodzi z komentarza @sebergsnp.einsum
może skorzystaćSSE2, ale ufuncs numpy nie będą dostępne aż do liczby 1,8 (zobaczdziennik zmian). Wierzę, że to poprawna odpowiedź, ale taknie byłem w stanie to potwierdzić. Pewne ograniczone dowody można znaleźć, zmieniając dtype tablicy wejściowej i obserwując różnicę prędkości oraz fakt, że nie wszyscy obserwują te same trendy w czasie.