मैं साइथन का उपयोग दो सरणियों को एक साथ जोड़ने में लगने वाले समय को कम करने के लिए करना चाहता हूं (तत्व-वार) Numpy सरणियों का उपयोग किए बिना बिना। मूल पायथन दृष्टिकोण जो मुझे सबसे तेज़ लगा, वह है सूची बोध का उपयोग करना, इस प्रकार है:

def add_arrays(a,b):
    return [m + n for m,n in zip(a,b)]

मेरा साइथन दृष्टिकोण थोड़ा अधिक जटिल है और यह इस प्रकार दिखता है:

from array import array
from libc.stdlib cimport malloc
from cython cimport boundscheck,wraparound

@boundscheck(False)
@wraparound(False)
cpdef add_arrays_Cython(int[:] Aarr, int[:] Barr):
    cdef size_t i, I
    I = Aarr.shape[0]
    cdef int *Carr = <int *> malloc(640000 * sizeof(int))
    for i in range(I):
        Carr[i] = Aarr[i]+Barr[i]
    result_as_array  = array('i',[e for e in Carr[:640000]])
    return result_as_array

ध्यान दें कि मैं इसे और भी तेज़ बनाने के लिए @boundscheck(False) और @wraparound(False) का उपयोग करता हूं। इसके अलावा, मैं एक बहुत बड़ी सरणी (आकार 640000) के बारे में चिंतित हूं और मैंने पाया कि अगर मैं केवल cdef int Carr[640000] का उपयोग करता हूं तो यह क्रैश हो जाता है, इसलिए मैंने malloc() का उपयोग किया, जिसने उस समस्या को हल किया। अंत में, मैं डेटा संरचना को पूर्णांक प्रकार के पायथन सरणी के रूप में वापस करता हूं।

कोड को प्रोफाइल करने के लिए मैंने निम्नलिखित चलाया:

a = array.array('i', range(640000)) #create integer array
b = a[:] #array to add

T=time.clock()
for i in range(20): add_arrays(a,b) #Python list comprehension approach
print(time.clock() - T)

>6.33 सेकंड

T=time.clock()
for i in range(20): add_arrays_Cython(a,b) #Cython approach
print(time.clock() - T)

> ४.५४ सेकंड

जाहिर है, साइथन-आधारित दृष्टिकोण लगभग 30% की गति देता है। मुझे उम्मीद थी कि स्पीड-अप परिमाण के क्रम के करीब होगा या इससे भी अधिक (जैसे यह नम्पी के लिए करता है)।

साइथन कोड को और तेज करने के लिए मैं क्या कर सकता हूं? क्या मेरे कोड में कोई स्पष्ट बाधाएं हैं? मैं साइथन के लिए शुरुआत कर रहा हूं इसलिए मुझे कुछ गलत लग रहा है।

2
CodeWanderer 31 मार्च 2020, 14:15

1 उत्तर

सबसे बढ़िया उत्तर

सबसे बड़ी अड़चन परिणाम सूचक को वापस एक सरणी में बदलना है।

यहाँ एक अनुकूलित संस्करण है:

from cython cimport boundscheck,wraparound
from cython cimport view

@boundscheck(False)
@wraparound(False)
cpdef add_arrays_Cython(int[:] Aarr, int[:] Barr):
    cdef size_t i, I
    I = Aarr.shape[0]
    result_as_array = view.array(shape=(I,), itemsize=sizeof(int), format='i')
    cdef int[:] Carr = result_as_array
    for i in range(I):
        Carr[i] = Aarr[i]+Barr[i]
    return result_as_array

यहां ध्यान देने योग्य कुछ बातें - एक अस्थायी बफर को मॉलोक करने और फिर परिणाम को एक सरणी में कॉपी करने के बजाय, मैं cython.view.array बना देता हूं और इसे int[:] पर डाल देता हूं। यह मुझे पॉइंटर एक्सेस की कच्ची गति देता है और अनावश्यक नकल से भी बचाता है। मैं साइथन ऑब्जेक्ट को सीधे एक पायथन ऑब्जेक्ट में परिवर्तित किए बिना भी वापस कर देता हूं। कुल मिलाकर, यह मुझे आपके मूल साइथन कार्यान्वयन की तुलना में 70x गति प्रदान करता है।

view ऑब्जेक्ट को सूची में बदलना मुश्किल साबित हुआ: यदि आप केवल रिटर्न स्टेटमेंट को return list(result_as_array) में बदलते हैं, तो कोड आपके प्रारंभिक कार्यान्वयन की तुलना में लगभग 10x धीमा हो जाता है। लेकिन अगर आप इस तरह रैपिंग की एक अतिरिक्त परत जोड़ते हैं: return list(memoryview(result_as_array)) फ़ंक्शन आपके संस्करण की तुलना में लगभग 5x तेज था। तो फिर, मुख्य ओवरहेड तेजी से देशी वस्तु से एक सामान्य पायथन में जा रहा था और यदि आपको तेज़ कोड की आवश्यकता है, तो इसे हमेशा टाला जाना चाहिए।

तुलना के लिए मैंने कोड को numpy. सुन्न संस्करण ने मेरे साइथन संस्करण की तरह ही तेजी से प्रदर्शन किया। इसका मतलब है कि सी कंपाइलर मेरे कोड के अंदर जोड़ीदार समन लूप को स्वचालित रूप से वेक्टर करने में सक्षम था।

साइड-नोट: आपको malloc()'d पॉइंटर्स पर free() को कॉल करने की आवश्यकता है, अन्यथा आप मेमोरी को लीक कर देते हैं।

2
Stefan Dragnev 31 मार्च 2020, 12:15