हैं numexpr , Numba और cython चारों ओर, इस उत्तर के लक्ष्य को ध्यान में इन संभावनाओं लेने के लिए है।
लेकिन पहले यह स्पष्ट करने दें: कोई फर्क नहीं पड़ता कि आप पायथन-फंक्शन को एक सुस्पष्ट-सरणी पर कैसे मैप करते हैं, यह एक पायथन फ़ंक्शन है, जिसका अर्थ है प्रत्येक मूल्यांकन के लिए:
- numpy-array element को Python-object (जैसे a
Float
) में परिवर्तित किया जाना चाहिए ।
- सभी गणना अजगर-वस्तुओं के साथ की जाती हैं, जिसका अर्थ है कि दुभाषिया, गतिशील प्रेषण और अपरिवर्तनीय वस्तुओं का ओवरहेड होना।
तो वास्तव में लूप के माध्यम से किस मशीनरी का उपयोग किया जाता है, ऊपर बताए गए ओवरहेड के कारण बड़ी भूमिका नहीं निभाती है - यह सुपीरियर की अंतर्निहित कार्यक्षमता का उपयोग करने की तुलना में बहुत धीमा रहता है।
आइए निम्नलिखित उदाहरण पर एक नज़र डालें:
# 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
दृष्टिकोण के शुद्ध-पायथन फ़ंक्शन वर्ग के प्रतिनिधि के रूप में चुना गया है। उपयोग करना perfplot
(इस उत्तर के परिशिष्ट में कोड देखें) हमें निम्नलिखित रनिंग टाइम मिलते हैं:
हम देख सकते हैं, कि शुद्ध अजगर संस्करण की तुलना में संख्यात्मक-दृष्टिकोण 10x-100x तेज है। बड़े सरणी-आकार के लिए प्रदर्शन में कमी संभवत: इसलिए है क्योंकि डेटा अब कैश को फिट नहीं करता है।
यह भी ध्यान देने योग्य है, कि vectorize
स्मृति का भी बहुत उपयोग होता है, इसलिए अक्सर मेमोरी-उपयोग बोतल-गर्दन (संबंधित एसओ-प्रश्न देखें ) है। यह भी ध्यान दें, उस खसरे के दस्तावेज में np.vectorize
कहा गया है कि यह "मुख्य रूप से सुविधा के लिए प्रदान किया गया है, प्रदर्शन के लिए नहीं"।
अन्य उपकरणों का उपयोग किया जाना चाहिए, जब प्रदर्शन वांछित होता है, तो खरोंच से सी-एक्सटेंशन लिखने के बाद, निम्नलिखित संभावनाएं होती हैं:
एक अक्सर सुनता है, कि सुन्न-प्रदर्शन जितना अच्छा होता है, क्योंकि यह हुड के नीचे शुद्ध सी होता है। फिर भी सुधार की बहुत गुंजाइश है!
वेक्टरित खस्ता-संस्करण बहुत अधिक अतिरिक्त मेमोरी और मेमोरी-एक्सेस का उपयोग करता है। न्यूमपी-लाइब्रेरी, खस्ता-सरणियों को टाइल करने की कोशिश करता है और इस तरह एक बेहतर कैश उपयोग प्राप्त होता है:
# 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")
निम्नलिखित तुलना की ओर जाता है:
मैं ऊपर दिए गए कथानक में सब कुछ स्पष्ट नहीं कर सकता: हम शुरुआत में अंक-पुस्तकालय के लिए बड़ा ओवरहेड देख सकते हैं, लेकिन क्योंकि यह कैश का बेहतर उपयोग करता है, यह बड़ी सरणियों के लिए लगभग 10 गुना तेज है!
एक अन्य दृष्टिकोण है फंक्शन को जिट-संकलित करना और इस प्रकार एक वास्तविक शुद्ध-सी UFunc प्राप्त करना। यह सुब्बा का दृष्टिकोण है:
# 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
यह मूल संख्या-दृष्टिकोण की तुलना में 10 गुना तेज है:
हालाँकि, कार्य शर्मनाक रूप से समानांतर है, इस प्रकार हम भी prange
समानांतर में लूप की गणना करने के लिए उपयोग कर सकते हैं :
@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
जैसा कि अपेक्षित था, छोटे आकार के लिए समानांतर फ़ंक्शन धीमा है, लेकिन बड़े आकारों के लिए तेज़ (लगभग कारक 2):
जबकि सुंबा, खस्ता-सरणियों के साथ संचालन को अनुकूलित करने में माहिर है, साइथन एक अधिक सामान्य उपकरण है। सुंबा के साथ समान प्रदर्शन को निकालने के लिए यह अधिक जटिल है - अक्सर यह स्थानीय कंपाइलर (gcc / MSVC) बनाम llvm (सुम्बा) के नीचे होता है:
%%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
साइथन कुछ हद तक धीमी गति से कार्य करता है:
निष्कर्ष
जाहिर है, केवल एक फ़ंक्शन के लिए परीक्षण कुछ भी साबित नहीं करता है। इसके अलावा, एक को ध्यान में रखना चाहिए, कि चुने हुए फ़ंक्शन-उदाहरण के लिए, मेमोरी की बैंडविड्थ 10 ^ 5 तत्वों से बड़े आकार के लिए बोतल की गर्दन थी - इस प्रकार इस क्षेत्र में सुंबा, सुपेक्सप्र और साइथन के लिए हमारे पास समान प्रदर्शन था।
अंत में, अल्टीमेटिव उत्तर फ़ंक्शन, हार्डवेयर, पायथन-वितरण और अन्य कारकों के प्रकार पर निर्भर करता है। उदाहरण के लिए एनाकोंडा-डिस्ट्रीब्यूशन इंटेल के वीएमएल का उपयोग सुपी के कार्यों के लिए करता है और इस प्रकार सुम्बा (जब तक कि यह एसवीएमएल का उपयोग नहीं करता है, तब तक एसओ-पोस्ट देखें ) exp
, जैसे ट्रांसडेंटल फ़ंक्शंस के लिए आसानी से sin
, cos
और इसी तरह - निम्न एसओ-पोस्ट जैसे उदाहरण देखें ।
फिर भी इस जाँच से और मेरे अनुभव से, मैं बताता हूँ, कि सुंबा को सबसे अच्छा प्रदर्शन सबसे आसान उपकरण लगता है, जब तक कि कोई भी ट्रान्सेंडैंटल फ़ंक्शन शामिल न हो।
परफेक्ट प्लॉट के साथ प्लॉटिंग रनिंग टाइम:
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)'
)