दो समान सूचियों में एक अलग मेमोरी फ़ुटप्रिंट क्यों होता है?


155

मैंने दो सूचियाँ बनाईं l1और l2हर एक को एक अलग रचना विधि के साथ बनाया:

import sys

l1 = [None] * 10
l2 = [None for _ in range(10)]

print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))

लेकिन आउटपुट ने मुझे चौंका दिया:

Size of l1 = 144
Size of l2 = 192

एक सूची समझ के साथ बनाई गई सूची स्मृति में एक बड़ा आकार है, लेकिन दो सूची पायथन में समान हैं अन्यथा।

ऐसा क्यों है? क्या यह कुछ सीपीथॉन की आंतरिक बात है, या कुछ अन्य स्पष्टीकरण है?


2
संभवतः, पुनरावृत्ति ऑपरेटर कुछ फ़ंक्शन को लागू करेगा जो अंतर्निहित सरणी को वास्तव में आकार देता है। ध्यान दें, 144 == sys.getsizeof([]) + 8*10)जहां 8 एक पॉइंटर का आकार है।
juanpa.arrivillaga

1
ध्यान दें कि यदि आप बदलते 10हैं 11, तो [None] * 11सूची में आकार है 152, लेकिन सूची की समझ अभी भी आकार में है 192। पहले से जुड़ा हुआ प्रश्न एक सटीक डुप्लिकेट नहीं है, लेकिन यह समझने में प्रासंगिक है कि ऐसा क्यों होता है।
पैट्रिक हॉग

जवाबों:


162

जब आप लिखते हैं [None] * 10, तो पायथन को पता होता है कि उसे ठीक 10 वस्तुओं की सूची की आवश्यकता होगी, इसलिए यह बिल्कुल उसी को आवंटित करता है।

जब आप एक सूची समझ का उपयोग करते हैं, तो पायथन को यह नहीं पता होता है कि इसकी कितनी आवश्यकता होगी। इसलिए यह धीरे-धीरे सूची में बढ़ता है क्योंकि तत्वों को जोड़ा जाता है। प्रत्येक प्राप्ति के लिए इसे तत्काल आवश्यकता से अधिक कमरा आवंटित किया जाता है, ताकि इसे प्रत्येक तत्व के लिए पुनः प्राप्त न करना पड़े। परिणामी सूची आवश्यकता से कुछ बड़ी होने की संभावना है।

समान आकारों के साथ बनाई गई सूचियों की तुलना करते समय आप इस व्यवहार को देख सकते हैं:

>>> sys.getsizeof([None]*15)
184
>>> sys.getsizeof([None]*16)
192
>>> sys.getsizeof([None for _ in range(15)])
192
>>> sys.getsizeof([None for _ in range(16)])
192
>>> sys.getsizeof([None for _ in range(17)])
264

आप देख सकते हैं कि पहला तरीका सिर्फ उसी चीज़ को आवंटित करता है जो आवश्यक है, जबकि दूसरा समय-समय पर बढ़ता है। इस उदाहरण में, यह 16 तत्वों के लिए पर्याप्त आवंटित करता है, और 17 वें तक पहुंचने पर इसे फिर से प्राप्त करना था।


1
हाँ, यह समझ में आता है। *जब मैं सामने का आकार जानता हूं, तो संभवत: यह बेहतर सूची बनाता है ।
लेडी केसली

27
@AndrejKesely केवल अपनी सूची में [x] * nअपरिवर्तनीय के साथ उपयोग xकरें। परिणामी सूची समान वस्तु के संदर्भ में होगी।
schwobaseggl

5
@schwobaseggl अच्छी तरह से, जो आप चाहते हैं वह हो सकता है, लेकिन यह समझना अच्छा है।
juanpa.arrivillaga

19
@ juanpa.arrivillaga सच, यह हो सकता है। लेकिन आम तौर पर ऐसा नहीं होता है और विशेष रूप से एसओ पोस्टर से भरा होता है और सोचता है कि उनका सारा डेटा एक साथ क्यों बदला: D
schwobaseggl

50

जैसा कि इस प्रश्न में उल्लेख किया गया है कि सूची-समझ list.append, हुड के तहत उपयोग करता है , इसलिए यह सूची-आकार परिवर्तन विधि को कॉल करेगा, जो समग्र करता है।

अपने आप को यह प्रदर्शित करने के लिए, आप वास्तव में disअसंतुष्ट का उपयोग कर सकते हैं :

>>> code = compile('[x for x in iterable]', '', 'eval')
>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x10560b810, file "", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x10560b810, file "", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>

कोड ऑब्जेक्ट LIST_APPENDके disassembly में ओपकोड को नोटिस करें <listcomp>। से डॉक्स :

LIST_APPEND (i)

कहता है list.append(TOS[-i], TOS)। सूची समझ को लागू करने के लिए उपयोग किया जाता है।

अब सूची-पुनरावृत्ति ऑपरेशन के लिए, हमारे पास इस बात का संकेत है कि यदि हम विचार करें तो क्या हो रहा है:

>>> import sys
>>> sys.getsizeof([])
64
>>> 8*10
80
>>> 64 + 80
144
>>> sys.getsizeof([None]*10)
144

तो, यह आकार को बिल्कुल आवंटित करने में सक्षम प्रतीत होता है । को देखते हुए स्रोत कोड , हम देखते हैं इस वास्तव में क्या होता है:

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;
    if (size == 0)
        return PyList_New(0);
    np = (PyListObject *) PyList_New(size);

अर्थात्, यहाँ size = Py_SIZE(a) * n;:। बाकी कार्य केवल सरणी भरते हैं।


"जैसा कि इस सवाल में कहा गया है कि सूची-समझ, हुड के नीचे list.append का उपयोग करता है" मुझे लगता है कि यह कहना अधिक सटीक है कि यह उपयोग करता है .extend()
संचय जूल

@ सनकी ऐसा क्यों मानते हो?
जुआनपा। श्रीविल्लागा

क्योंकि यह तत्वों को एक-एक करके जोड़ नहीं रहा है। जब आप किसी सूची में तत्वों को जोड़ते हैं, तो आप वास्तव में एक नई सूची बना रहे होते हैं, एक नई मेमोरी आवंटन के साथ और उस नए मेमोरी आवंटन में सूची डालते हैं। दूसरी ओर, सूची की समझ, अधिकांश नए तत्वों को स्मृति में डालें जो पहले से ही आवंटित किए गए हैं, और जब वे आवंटित स्मृति से बाहर निकलते हैं, तो वे स्मृति का एक और चक आवंटित करते हैं, न कि नए तत्व के लिए पर्याप्त।
संचय जूल

7
@Accumulation यह गलत है। list.appendएक परिशोधित निरंतर समय संचालन है क्योंकि जब एक सूची का आकार बदलता है, तो यह समग्र हो जाता है। प्रत्येक अपेंडेंट ऑपरेशन नहीं, इसलिए, एक नए आवंटित सरणी में परिणाम होता है। किसी भी घटना में जो प्रश्न मैं आपको स्रोत कोड में दिखाता हूं कि वास्तव में, सूची की समझ का उपयोग करते हैं list.append,। मैं एक पल में अपने लैपटॉप पर वापस आ जाएगा और मैं तुम्हें एक सूची समझ के लिए disassembled बाईटकोड और इसी दिखा सकते हैं LIST_APPENDopcode
juanpa.arrivillaga

3

कोई भी स्मृति का एक खंड नहीं है, लेकिन यह पूर्व-निर्दिष्ट आकार नहीं है। उस के अलावा, सरणी तत्वों के बीच एक सरणी में कुछ अतिरिक्त रिक्ति है। आप इसे स्वयं चलाकर देख सकते हैं:

for ele in l2:
    print(sys.getsizeof(ele))

>>>>16
16
16
16
16
16
16
16
16
16

जो कि एल 2 के आकार का कुल नहीं है, बल्कि कम है।

print(sys.getsizeof([None]))
72

और यह आकार के दसवें हिस्से से बहुत अधिक है l1

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


1
Noneवास्तव में अंतर्निहित सरणी में संग्रहीत नहीं है, केवल एक चीज जो संग्रहीत है वह एक PyObjectसूचक (8 बाइट्स) है। सभी अजगर वस्तुओं को ढेर पर आवंटित किया जाता है। Noneएक सिंगलटन है, इसलिए बहुत सारे नॉन के साथ एक सूची है बस Noneढेर पर एक ही ऑब्जेक्ट के लिए PyObject पॉइंटर्स की एक सरणी बनाएगी (और अतिरिक्त में प्रक्रिया में अतिरिक्त मेमोरी का उपयोग नहीं करें None)। मुझे यकीन नहीं है कि आप क्या मतलब है "कोई भी एक पूर्व-निर्दिष्ट आकार नहीं है", लेकिन यह सही नहीं लगता है। अंत में, getsizeofप्रत्येक तत्व के साथ आपका लूप प्रदर्शन नहीं कर रहा है जो आपको लगता है कि यह प्रदर्शन कर रहा है।
juanpa.arrivillaga

यदि आप कहते हैं कि यह सच है, तो [कोई नहीं] * 10 का आकार [कोई भी] के आकार के समान होना चाहिए। लेकिन स्पष्ट रूप से ऐसा नहीं है - कुछ अतिरिक्त भंडारण को जोड़ा गया है। वास्तव में, [कोई नहीं] का आकार दस गुना (१६०) दोहराया गया है [दस] के आकार से भी कम [कोई नहीं]। जैसा कि आप बताते हैं, स्पष्ट रूप से सूचक का आकार [कोई नहीं] अपने आप में [कोई नहीं] के आकार (72 बाइट्स के बजाय 16 बाइट्स) से छोटा है। हालाँकि, 160 + 32 192 है। मुझे नहीं लगता कि पूर्ववर्ती उत्तर समस्या को पूरी तरह से हल करता है। यह स्पष्ट है कि कुछ अतिरिक्त छोटी मात्रा में मेमोरी (शायद मशीन राज्य पर निर्भर) आवंटित की जाती है।
StevenJD

"यदि आप कहते हैं कि यह सच है, तो [कोई नहीं] * 10 का आकार [कोई भी] के आकार के समान होना चाहिए" मैं जो कह रहा हूं वह संभवतः इसका मतलब हो सकता है? फिर, आप इस तथ्य पर ध्यान केंद्रित कर रहे हैं कि अंतर्निहित बफर ओवर-आबंटित है, या यह कि सूची के आकार में अंतर्निहित बफर के आकार से अधिक शामिल है (यह निश्चित रूप से करता है), लेकिन इसका मतलब यह नहीं है यह प्रश्न। फिर से, के आपके उपयोग gestsizeofसे प्रत्येक पर eleकी l2भ्रामक है क्योंकि getsizeof(l2) खाते में कंटेनर के अंदर तत्वों का आकार नहीं ले करता है
juanpa.arrivillaga

अपने आप को साबित करने के लिए कि आखिरी दावा, l1 = [None]; l2 = [None]*100; l3 = [l2]तब करें print(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))। आप की तरह एक परिणाम मिल जाएगा: 72 864 72। है, क्रमशः, 64 + 1*8, 64 + 100*8, और 64 + 1*8, फिर से, 8 बाइट सूचक आकार के साथ एक 64 बिट प्रणाली संभालने।
juanpa.arrivillaga

1
जैसा कि मैंने कहा है, sys.getsizeof* कंटेनर में वस्तुओं के आकार का हिसाब नहीं है। से डॉक्स : "केवल स्मृति की खपत सीधे वस्तु के लिए जिम्मेदार ठहराया, के लिए जिम्मेदार है नहीं वस्तुओं की स्मृति की खपत यह संदर्भित करता है ... देखें पुनरावर्ती sizeof getsizeof का उपयोग कर () रिकर्सिवली कंटेनर के आकार का पता लगाने और करने का एक उदाहरण के लिए नुस्खा उनकी सभी सामग्री। "
juanpa.arrivillaga
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.