पायथन की सरणियाँ धीमी क्यों हैं?


153

मुझे array.arrayसूचियों की तुलना में तेज़ होने की उम्मीद थी, क्योंकि सरणियाँ अनबॉक्स की जाती हैं।

हालाँकि, मुझे निम्न परिणाम मिले:

In [1]: import array

In [2]: L = list(range(100000000))

In [3]: A = array.array('l', range(100000000))

In [4]: %timeit sum(L)
1 loop, best of 3: 667 ms per loop

In [5]: %timeit sum(A)
1 loop, best of 3: 1.41 s per loop

In [6]: %timeit sum(L)
1 loop, best of 3: 627 ms per loop

In [7]: %timeit sum(A)
1 loop, best of 3: 1.39 s per loop

ऐसे अंतर का कारण क्या हो सकता है?


4
सुन्न उपकरण कुशलता से आपके सरणी का फायदा उठा सकते हैं:% timeit np.sum (ए): 100 लूप्स, सर्वश्रेष्ठ 3: 8.87 एमएस प्रति लूप
बीएम

6
मैं कभी भी ऐसी स्थिति में नहीं आया हूँ जहाँ मुझे arrayपैकेज का उपयोग करने की आवश्यकता हो । यदि आप महत्वपूर्ण मात्रा में गणित करना चाहते हैं, तो Numpy प्रकाश-गति (यानी C) पर संचालित होता है, और आमतौर पर चीजों की भोली कार्यान्वयन से बेहतर होता है sum())।
निक टी

40
करीबी मतदाता: वास्तव में यह राय आधारित क्यों है? ओपी एक विशिष्ट, तकनीकी प्रश्न को मापने योग्य और दोहराने योग्य घटना के बारे में पूछता प्रतीत होता है।
केविन

5
@NickT एक अनुकूलन किस्सा पढ़ें । पता चला है arrayएक करने के लिए पूर्णांक (ASCII का प्रतिनिधित्व बाइट्स) के एक स्ट्रिंग परिवर्तित करने में बहुत तेजी से है strवस्तु। खुद गुइडो ने बहुत सारे अन्य समाधान के बाद ही इस पर ध्यान दिया और प्रदर्शन पर काफी हैरान थे। वैसे भी यह एकमात्र ऐसी जगह है जहाँ मुझे याद है कि यह उपयोगी है। numpyसरणियों से निपटने के लिए बहुत बेहतर है, लेकिन यह एक 3 पार्टी निर्भरता है।
बकुरीउ

जवाबों:


220

भंडारण "अनबॉक्स्ड" है, लेकिन हर बार जब आप "बॉक्स" के लिए एक तत्व अजगर है का उपयोग यह आदेश इसके साथ कुछ भी करने को में (एक नियमित रूप से अजगर वस्तु में एम्बेड)। उदाहरण के लिए, आपका sum(A)सरणी सरणी पर पुनरावृत्त होता है, और प्रत्येक पूर्णांक को एक नियमित रूप से पायथन intऑब्जेक्ट में बॉक्स करता है । वह समय खर्च करता है। अपने मेंsum(L) , सभी बॉक्सिंग उस समय बनाई गई थी जब सूची बनाई गई थी।

तो, अंत में, एक सरणी आमतौर पर धीमी होती है, लेकिन इसके लिए काफी कम मेमोरी की आवश्यकता होती है।


यहाँ पायथन 3 के हालिया संस्करण से प्रासंगिक कोड है, लेकिन पायथॉन को पहली बार जारी किए जाने के बाद से सभी सीपीटीयन कार्यान्वयन के लिए समान मूल विचार लागू होते हैं।

यहाँ एक सूची आइटम का उपयोग करने के लिए कोड है:

PyObject *
PyList_GetItem(PyObject *op, Py_ssize_t i)
{
    /* error checking omitted */
    return ((PyListObject *)op) -> ob_item[i];
}

इसके लिए बहुत कम है: सूची में somelist[i]केवल i'वें ऑब्जेक्ट' (और सीपीथॉन में सभी पायथन ऑब्जेक्ट्स एक संरचना के संकेत हैं, जिसका प्रारंभिक खंड लेआउट के अनुरूप है struct PyObject)।

और यहां __getitem__एक arrayप्रकार कोड के साथ कार्यान्वयन है l:

static PyObject *
l_getitem(arrayobject *ap, Py_ssize_t i)
{
    return PyLong_FromLong(((long *)ap->ob_item)[i]);
}

कच्ची मेमोरी को प्लेटफॉर्म-मूल C longपूर्णांकों के वेक्टर के रूप में माना जाता है ; i'वें C longपढ़ा है; और फिर पायथन ऑब्जेक्ट में PyLong_FromLong()मूल ("बॉक्स") को लपेटने के लिए कहा जाता है (जो, पायथन 3 में, जो पायथन 2 के बीच के अंतर को समाप्त करता है और , वास्तव में प्रकार के रूप में दिखाया गया है )।C longlongintlongint

इस मुक्केबाजी को पायथन intऑब्जेक्ट के लिए नई मेमोरी आवंटित करनी है , और इसमें देशी C longबिट्स को स्प्रे करना है । मूल उदाहरण के संदर्भ में, इस वस्तु का जीवनकाल बहुत ही संक्षिप्त है (केवल sum()एक कुल मिलाकर सामग्री को जोड़ने के लिए पर्याप्त है ), और फिर नए से निपटने के लिए अधिक समय की आवश्यकता हैint वस्तु ।

यह वह जगह है जहां गति अंतर से आता है, हमेशा से आया है, और हमेशा सीपीथॉन कार्यान्वयन से आएगा।


87

टिम पीटर्स के उत्कृष्ट उत्तर को जोड़ने के लिए, एफ़रे बफर प्रोटोकॉल को लागू करते हैं , जबकि सूचियाँ नहीं। इसका मतलब यह है कि, यदि आप एक C एक्सटेंशन (या नैतिक समतुल्य, जैसे साइथॉन मॉड्यूल लिखना ) लिख रहे हैं , तो आप किसी ऐरे के तत्वों के साथ बहुत तेजी से काम कर सकते हैं और काम कर सकते हैं, जो पायथन कर सकता है। यह आपको काफी गति में सुधार देगा, संभवतः परिमाण के एक क्रम पर। हालाँकि, इसमें कई डाउनसाइड हैं:

  1. आप अब पायथन के बजाय C लिखने के व्यवसाय में हैं। साइथॉन इस बात को स्पष्ट करने का एक तरीका है, लेकिन यह भाषाओं के बीच कई बुनियादी अंतरों को खत्म नहीं करता है; आपको सी शब्दार्थ से परिचित होना चाहिए और यह समझना चाहिए कि यह क्या कर रहा है।
  2. PyPy का C API कुछ हद तक काम करता है , लेकिन बहुत तेज़ नहीं है। यदि आप PyPy को लक्षित कर रहे हैं, तो आपको शायद नियमित सूचियों के साथ सरल कोड लिखना चाहिए, और फिर JITTER को आपके लिए इसे अनुकूलित करने दें।
  3. सी एक्सटेंशन शुद्ध पायथन कोड की तुलना में वितरित करने के लिए कठिन हैं क्योंकि उन्हें संकलित करने की आवश्यकता है। संकलन वास्तुकला और ऑपरेटिंग-सिस्टम पर निर्भर करता है, इसलिए आपको यह सुनिश्चित करने की आवश्यकता होगी कि आप अपने लक्ष्य मंच के लिए संकलन कर रहे हैं।

सी एक्सटेंशन में सीधे जाने से आपके उपयोग के मामले के आधार पर, एक स्लेटेडहैमर का उपयोग एक स्वाट करने के लिए किया जा सकता है। आपको सबसे पहले NumPy की जांच करनी चाहिए और यह देखना चाहिए कि आप जो भी गणित करने की कोशिश कर रहे हैं वह करने के लिए पर्याप्त शक्तिशाली है या नहीं। अगर इसे सही तरीके से इस्तेमाल किया जाए तो यह देशी पायथन से भी ज्यादा तेज होगा।


10

टिम पीटर्स ने जवाब दिया कि यह धीमा क्यों है, लेकिन आइए देखें कि इसे कैसे सुधारें

आपके उदाहरण के लिए चिपके रहना sum(range(...))(आपके उदाहरण के लिए यहां से स्मृति में फिट होने के लिए कारक 10 छोटा):

import numpy
import array
L = list(range(10**7))
A = array.array('l', L)
N = numpy.array(L)

%timeit sum(L)
10 loops, best of 3: 101 ms per loop

%timeit sum(A)
1 loop, best of 3: 237 ms per loop

%timeit sum(N)
1 loop, best of 3: 743 ms per loop

इस तरह से बॉक्स / अनबॉक्स को सुन्न करने की जरूरत है, जिसमें अतिरिक्त ओवरहेड है। इसे तेजी से बनाने के लिए एक बराबर सी कोड के भीतर रहना होगा:

%timeit N.sum()
100 loops, best of 3: 6.27 ms per loop

लिस्ट सॉल्यूशन से लेकर सिम्पी वर्जन तक रनटाइम में यह एक फैक्टर 16 है।

आइए यह भी जांचें कि उन डेटा संरचनाओं को बनाने में कितना समय लगता है

%timeit list(range(10**7))
1 loop, best of 3: 283 ms per loop

%timeit array.array('l', range(10**7))
1 loop, best of 3: 884 ms per loop

%timeit numpy.array(range(10**7))
1 loop, best of 3: 1.49 s per loop

%timeit numpy.arange(10**7)
10 loops, best of 3: 21.7 ms per loop

स्पष्ट विजेता: Numpy

यह भी ध्यान दें कि डेटा संरचना बनाने में योग के रूप में ज्यादा समय लगता है, यदि अधिक नहीं। मेमोरी का आवंटन धीमा है।

उन का मेमोरी उपयोग:

sys.getsizeof(L)
90000112
sys.getsizeof(A)
81940352
sys.getsizeof(N)
80000096

इसलिए ये अलग-अलग ओवरहेड के साथ प्रति संख्या 8 बाइट लेते हैं। जिस रेंज के लिए हम 32 बिट का उपयोग करते हैं, वह पर्याप्त है, इसलिए हम कुछ मेमोरी को सुरक्षित कर सकते हैं।

N=numpy.arange(10**7, dtype=numpy.int32)

sys.getsizeof(N)
40000096

%timeit N.sum()
100 loops, best of 3: 8.35 ms per loop

लेकिन यह पता चला है कि मेरी मशीन पर 64 बिट इनट्स को जोड़ना 32 बिट्स की तुलना में तेज है, इसलिए यह केवल इसके लायक है यदि आप मेमोरी / बैंडविड्थ द्वारा सीमित हैं।


-1

कृपया ध्यान दें कि नहीं के 100000000बराबर है , और मेरे परिणाम के रूप में कर रहे हैं:10^810^7

100000000 == 10**8

# my test results on a Linux virtual machine:
#<L = list(range(100000000))> Time: 0:00:03.263585
#<A = array.array('l', range(100000000))> Time: 0:00:16.728709
#<L = list(range(10**8))> Time: 0:00:03.119379
#<A = array.array('l', range(10**8))> Time: 0:00:18.042187
#<A = array.array('l', L)> Time: 0:00:07.524478
#<sum(L)> Time: 0:00:01.640671
#<np.sum(L)> Time: 0:00:20.762153
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.