Orada numexpr , numba ve Cython etrafında, bu cevabın amacı dikkate bu olasılıkları almaktır.
Ama önce açık olanı belirtelim: Bir Python işlevini bir numpy dizisine nasıl eşleseniz de, Python işlevi kalır, yani her değerlendirme için:
- numpy-dizi elemanı bir Python-nesnesine dönüştürülmelidir (örneğin a
Float
).
- tüm hesaplamalar Python nesneleriyle yapılır, yani yorumlayıcı yükü, dinamik dağıtım ve değişmez nesneler vardır.
Bu yüzden, dizi boyunca döngü yapmak için hangi makinelerin kullanıldığı yukarıda belirtilen ek yük nedeniyle büyük bir rol oynamaz - numpy'nin yerleşik işlevini kullanmaktan çok daha yavaş kalır.
Aşağıdaki örneğe bakalım:
# numpy-functionality
def f(x):
return x+2*x*x+4*x*x*x
# python-function as ufunc
import numpy as np
vf=np.vectorize(f)
vf.__name__="vf"
np.vectorize
saf-python fonksiyon sınıfının bir temsili olarak seçilir. Kullanarak perfplot
(bu cevabın ekindeki koda bakınız) aşağıdaki çalışma sürelerini elde ederiz:
Numpy yaklaşımının saf python versiyonundan 10x-100x daha hızlı olduğunu görebiliriz. Daha büyük dizi boyutları için performansın düşmesi, büyük olasılıkla verilerin artık önbelleğe sığmamasıdır.
Ayrıca vectorize
çok fazla bellek kullanan , ayrıca bellek kullanımı şişe boynudur (ilgili SO sorusuna bakın ). Ayrıca, numpy'nin np.vectorize
"performans için değil öncelikle kolaylık için sağlandığını" belirten belgelere dikkat edin .
Diğer araçlar kullanılmalıdır, performans istendiğinde, sıfırdan bir C uzantısı yazmanın yanı sıra, aşağıdaki olasılıklar vardır:
Sık sık, numpy performansının elde ettiği kadar iyi olduğunu duyarız, çünkü kaputun altında saf C'dir. Yine de iyileştirilmesi gereken çok şey var!
Vektörize edilmiş numpy sürümü çok fazla ek bellek ve bellek erişimi kullanır. Numexp kütüphanesi, numpy dizilerini döşemeye çalışır ve böylece daha iyi bir önbellek kullanımı elde eder:
# less cache misses than numpy-functionality
import numexpr as ne
def ne_f(x):
return ne.evaluate("x+2*x*x+4*x*x*x")
Aşağıdaki karşılaştırmaya yönlendirir:
Yukarıdaki tablodaki her şeyi açıklayamıyorum: başlangıçta numexpr-kütüphane için daha büyük bir ek yük görüyoruz, ancak önbelleği daha iyi kullandığından daha büyük diziler için yaklaşık 10 kat daha hızlı!
Başka bir yaklaşım, fonksiyonu jit-derlemek ve böylece gerçek bir saf C UFunc elde etmektir. Bu numba'nın yaklaşımı:
# runtime generated C-function as ufunc
import numba as nb
@nb.vectorize(target="cpu")
def nb_vf(x):
return x+2*x*x+4*x*x*x
Orijinal numpy yaklaşımından 10 kat daha hızlıdır:
Bununla birlikte, görev utanç verici bir şekilde paralelleştirilebilir, bu nedenle prange
döngüyü paralel olarak hesaplamak için de kullanabiliriz :
@nb.njit(parallel=True)
def nb_par_jitf(x):
y=np.empty(x.shape)
for i in nb.prange(len(x)):
y[i]=x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y
Beklendiği gibi, paralel işlev daha küçük girişler için daha yavaş, ancak daha büyük boyutlar için daha hızlı (neredeyse faktör 2):
Numba, numpy dizileriyle işlemleri optimize etme konusunda uzmanlaşırken, Cython daha genel bir araçtır. Numba ile aynı performansı elde etmek daha karmaşıktır - genellikle yerel derleyiciye (gcc / MSVC) karşı llvm (numba) değerine düşer:
%%cython -c=/openmp -a
import numpy as np
import cython
#single core:
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_f(double[::1] x):
y_out=np.empty(len(x))
cdef Py_ssize_t i
cdef double[::1] y=y_out
for i in range(len(x)):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
#parallel:
from cython.parallel import prange
@cython.boundscheck(False)
@cython.wraparound(False)
def cy_par_f(double[::1] x):
y_out=np.empty(len(x))
cdef double[::1] y=y_out
cdef Py_ssize_t i
cdef Py_ssize_t n = len(x)
for i in prange(n, nogil=True):
y[i] = x[i]+2*x[i]*x[i]+4*x[i]*x[i]*x[i]
return y_out
Cython, biraz daha yavaş fonksiyonlarla sonuçlanır:
Sonuç
Açıkçası, sadece bir işlev için test yapmak hiçbir şey kanıtlamaz. Ayrıca, seçilen işlev örneği için, belleğin bant genişliğinin 10 ^ 5 öğeden daha büyük boyutlar için şişe boynu olduğu akılda tutulmalıdır - bu nedenle bu bölgedeki numba, numexpr ve cython için aynı performansa sahibiz.
Sonunda, nihai cevap fonksiyonun tipine, donanıma, Python dağılımı ve diğer faktörlere bağlıdır. Örnek Anaconda-dağıtım için numpy en işlevleri için Intel'in VML'yi kullanmaktadır ve böylece Numba Mağazasından (o SVML kullandığı sürece bu bkz SO-yazı transandantal fonksiyonlar gibi kolayca için) exp
, sin
, cos
ve benzeri - örneğin aşağıdakilere bakın SO-posta .
Yine de bu araştırmadan ve şimdiye kadar edindiğim deneyimlerden yola çıkarak, aşkın işlevlerin dahil olmadığı sürece numba'nın en iyi performansa sahip en kolay araç gibi göründüğünü söyleyebilirim.
Perfplot -package ile çalışma sürelerini çizme :
import perfplot
perfplot.show(
setup=lambda n: np.random.rand(n),
n_range=[2**k for k in range(0,24)],
kernels=[
f,
vf,
ne_f,
nb_vf, nb_par_jitf,
cy_f, cy_par_f,
],
logx=True,
logy=True,
xlabel='len(x)'
)