सूची समझ से थोड़ा अधिक मेमोरी का उपयोग करता है


79

इसलिए मैं listवस्तुओं के साथ खेल रहा था और थोड़ी अजीब बात यह पाई listगई कि अगर list()इसके साथ बनाया गया है, तो सूची बोध की तुलना में अधिक स्मृति का उपयोग होता है? मैं पायथन 3.5.2 का उपयोग कर रहा हूं

In [1]: import sys
In [2]: a = list(range(100))
In [3]: sys.getsizeof(a)
Out[3]: 1008
In [4]: b = [i for i in range(100)]
In [5]: sys.getsizeof(b)
Out[5]: 912
In [6]: type(a) == type(b)
Out[6]: True
In [7]: a == b
Out[7]: True
In [8]: sys.getsizeof(list(b))
Out[8]: 1008

से डॉक्स :

सूचियों का निर्माण कई तरीकों से किया जा सकता है:

  • खाली सूची को दर्शाने के लिए चौकोर कोष्ठकों का उपयोग करना: []
  • वर्ग कोष्ठक का उपयोग करना, अल्पविराम के साथ आइटम को अलग: [a],[a, b, c]
  • एक सूची समझ का उपयोग करना: [x for x in iterable]
  • टाइप कंस्ट्रक्टर का उपयोग करना: list()याlist(iterable)

लेकिन ऐसा लगता है कि list()इसका उपयोग करने से अधिक मेमोरी का उपयोग होता है।

और जितना listबड़ा होता है, खाई बढ़ती जाती है।

स्मृति में अंतर

ऐसा क्यूँ होता है?

अद्यतन # 1

पायथन 3.6.0b2 के साथ टेस्ट:

Python 3.6.0b2 (default, Oct 11 2016, 11:52:53) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(list(range(100)))
1008
>>> sys.getsizeof([i for i in range(100)])
912

अद्यतन # 2

पायथन 2.7.12 के साथ टेस्ट:

Python 2.7.12 (default, Jul  1 2016, 15:12:24) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(list(xrange(100)))
1016
>>> sys.getsizeof([i for i in xrange(100)])
920

3
यह एक बहुत ही दिलचस्प सवाल है। मैं पायथन 3.4.3 में घटना को पुन: पेश कर सकता हूं। और भी दिलचस्प: पायथन 2.7.5 sys.getsizeof(list(range(100)))पर 1016 है, getsizeof(range(100))872 है और getsizeof([i for i in range(100)])920 है। सभी का प्रकार है list
स्वेन फेस्टर्सन

दिलचस्पी की बात यह है कि यह अंतर पाइथन 2.7.10 में भी है (हालाँकि वास्तविक संख्या पाइथन 3 से अलग हैं)। इसके अलावा वहाँ 3.5 और 3.6 बी में।
cdarke

मुझे पायथन 2.7.6 के लिए @SvenFestersen के समान नंबर मिलते हैं, उपयोग करते समय भी xrange
रेमकोगर्लिच

2
यहाँ एक संभावित व्याख्या है: stackoverflow.com/questions/7247298/size-of-list-in-memory । यदि विधियों में से एक का उपयोग करके सूची बनाता है append(), तो स्मृति का अधिक आवंटन हो सकता है। मुझे लगता है कि वास्तव में यह स्पष्ट करने का एकमात्र तरीका है कि पायथन स्रोतों पर एक नज़र डालें।
स्वेन फेस्टर्सन

केवल 10% अधिक (आप वास्तव में ऐसा कहीं नहीं कहते हैं)। मैं शीर्षक "थोड़ा और" के लिए फिर से लिखना चाहता हूँ।
smci

जवाबों:


61

मुझे लगता है कि आप ओवर-आवंटन पैटर्न देख रहे हैं यह स्रोत से एक नमूना है :


0-88 लंबाई के सूची बोध के आकारों को प्रिंट करते हुए आप पैटर्न मैच देख सकते हैं:

# create comprehensions for sizes 0-88
comprehensions = [sys.getsizeof([1 for _ in range(l)]) for l in range(90)]

# only take those that resulted in growth compared to previous length
steps = zip(comprehensions, comprehensions[1:])
growths = [x for x in list(enumerate(steps)) if x[1][0] != x[1][1]]

# print the results:
for growth in growths:
    print(growth)

परिणाम (प्रारूप है (list length, (old total size, new total size))):

(0, (64, 96)) 
(4, (96, 128))
(8, (128, 192))
(16, (192, 264))
(25, (264, 344))
(35, (344, 432))
(46, (432, 528))
(58, (528, 640))
(72, (640, 768))
(88, (768, 912))

ओवर-आवंटन प्रदर्शन कारणों के लिए किया जाता है जिससे सूचियों को हर विकास (बेहतर परिशोधित प्रदर्शन) के साथ अधिक मेमोरी आवंटित किए बिना बढ़ने की अनुमति मिलती है ।

सूची समझ का उपयोग करने के साथ अंतर का एक संभावित कारण, यह है कि सूची की समझ उत्पन्न सूची के आकार की गणना नहीं कर सकती है, लेकिन list() कर सकती है। इसका मतलब यह है कि समझ लगातार बढ़ेगी क्योंकि यह ओवर-एलोकेशन का उपयोग करके इसे भरता है जब तक कि इसे अंतिम रूप से नहीं भरा जाता है।

यह संभव है कि एक बार किए गए अप्रयुक्त नोड्स के साथ ओवर-आवंटन बफर नहीं बढ़ेगा (वास्तव में, ज्यादातर मामलों में यह अभ्यस्त है, जो ओवर-आवंटन उद्देश्य को हरा देगा)।

list()हालाँकि, कुछ बफर को सूची के आकार से जोड़ सकते हैं क्योंकि यह अंतिम सूची आकार को पहले से जानता है।


एक और समर्थन सबूत, स्रोत से भी, जो हम देखते हैं सूचीLIST_APPENDlist.resize बोध को , जो उपयोग का संकेत देता है , जो बदले में पूर्व-आवंटन बफर का उपभोग करने का संकेत देता है बिना यह जाने कि यह कितना भरा जाएगा। यह आपके द्वारा देखे जा रहे व्यवहार के अनुरूप है।


निष्कर्ष निकालने के लिए, list()सूची आकार के एक फ़ंक्शन के रूप में अधिक नोड्स को पूर्व-आवंटित करेगा

>>> sys.getsizeof(list([1,2,3]))
60
>>> sys.getsizeof(list([1,2,3,4]))
64

सूची की समझ सूची के आकार को नहीं जानती है, इसलिए यह अपग्रेड ऑपरेशन का उपयोग करता है क्योंकि यह बढ़ता है, पूर्व-आवंटन बफर को कम करता है:

# one item before filling pre-allocation buffer completely
>>> sys.getsizeof([i for i in [1,2,3]]) 
52
# fills pre-allocation buffer completely
# note that size did not change, we still have buffered unused nodes
>>> sys.getsizeof([i for i in [1,2,3,4]]) 
52
# grows pre-allocation buffer
>>> sys.getsizeof([i for i in [1,2,3,4,5]])
68

4
लेकिन ओवर-एलाटॉन एक के साथ क्यों होगा लेकिन दूसरे के साथ नहीं?
cdarke

यह विशेष रूप से है list.resize। मैं स्रोत के माध्यम से नेविगेट करने में एक विशेषज्ञ नहीं हूं, लेकिन अगर एक कॉल आकार बदलता है और दूसरा नहीं करता है - तो यह अंतर की व्याख्या कर सकता है।
रीट शरबानी

6
यहां पायथन 3.5.2। लूप में 0 से 35 तक सूचियों के मुद्रण का प्रयास करें। सूची के लिए मैं देख रहा हूँ 64, 96, 104, 112, 120, 128, 136, 144, 160, 192, 200, 208, 216, 224, 232, 240, 256, 264, 272, 280, 288, 296, 304, 312, 328, 336, 344, 352, 360, 368, 376, 384, 400, 408, 416और समझ के लिए 64, 96, 96, 96, 96, 128, 128, 128, 128, 192, 192, 192, 192, 192, 192, 192, 192, 264, 264, 264, 264, 264, 264, 264, 264, 264, 344, 344, 344, 344, 344, 344, 344, 344, 344। मैं उस समझ को छोड़कर जो स्मृति को उपदेश देने के लिए प्रतीत होता है वह एल्गोरिथ्म है जो कुछ आकारों के लिए अधिक रैम का उपयोग करता है।
तवाओ

मैं वही उम्मीद करूंगा। मैं जल्द ही इसमें आगे देख सकता हूं। अच्छी टिप्पणियाँ।
रीट शरबानी

4
वास्तव में list()सूची आकार निर्धारित करता है, जो सूची समझ नहीं कर सकता है। इससे पता चलता है कि सूची की समझ हमेशा सूची के "अंतिम" विकास को "ट्रिगर" नहीं करती है। समझ में आ सकता है।
रीट शरबानी

30

उस भयानक अजगर को समझने में मेरी मदद करने के लिए सभी को धन्यवाद।

मैं यह सवाल नहीं करना चाहता कि बड़े पैमाने पर (इसीलिए मैं उत्तर पोस्ट कर रहा हूं), बस अपने विचारों को दिखाना और साझा करना चाहता हूं।

जैसा कि @ReutSharabani ने सही उल्लेख किया: "सूची () निश्चित रूप से सूची आकार निर्धारित करता है"। आप इसे उस ग्राफ से देख सकते हैं।

आकार का ग्राफ

जब आप appendया सूची बोध का उपयोग करते हैं, तो आपके पास हमेशा कुछ प्रकार की सीमाएं होती हैं जो कुछ बिंदु तक पहुंचने पर फैली होती हैं। और आपके साथ list()लगभग समान सीमाएं हैं, लेकिन वे तैर रहे हैं।

अपडेट करें

तो @ReutSharabani , @tavo , @SvenFestersen को धन्यवाद

योग करने के लिए: list()उपदेश मेमोरी सूची के आकार पर निर्भर करती है, सूची की समझ ऐसा नहीं कर सकती है (यह आवश्यकता होने पर अधिक मेमोरी का अनुरोध करती है, जैसे .append())। इसलिए list()अधिक मेमोरी स्टोर करें।

एक और ग्राफ, जो list()उपदेशात्मक स्मृति को दर्शाता है । इसलिए ग्रीन लाइन list(range(830))तत्व द्वारा तत्व को जोड़ती है और थोड़ी देर के लिए स्मृति नहीं बदलती है।

सूची () स्मृति का प्रचार करती है

अद्यतन २

@Barmar के रूप में, नीचे टिप्पणी में बताया गया है list()मुझे तेजी से सूची समझ से, चाहिए तो मैं भागा timeit()साथ number=1000की लंबाई के लिए listसे 4**0करने के लिए 4**10और परिणाम हैं

समय माप


1
उत्तर क्यों है कि लाल रेखा नीले से ऊपर है, कि जब listनिर्माता नई सूची के आकार को इस तर्क से निर्धारित कर सकता है कि यह अभी भी अंतरिक्ष की उतनी ही मात्रा का प्रचार करेगा क्योंकि यदि अंतिम तत्व बस वहां गया और इसके लिए पर्याप्त स्थान नहीं था। कम से कम मेरे लिए यही मायने रखता है।
तवो

@tavo मुझे ऐसा ही लगता है, कुछ पल बाद मैं इसे ग्राफ में दिखाना चाहता हूं।
vishes_shell

2
इसलिए जबकि सूची की समझ कम मेमोरी का उपयोग करती है, वे संभवतः सभी आकार के होने के कारण काफी धीमी हो जाती हैं। इन्हें अक्सर सूची को एक नए मेमोरी क्षेत्र में कॉपी करना होगा।
बरमार

@ बरमार वास्तव में मैं rangeऑब्जेक्ट के साथ कुछ समय माप चला सकता हूं (जो मजेदार हो सकता है)।
vishes_shell

और यह आपके रेखांकन को भी सुंदर बना देगा। :)
बारमर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.