ट्यूपल्स सूची की तुलना में स्मृति में कम जगह क्यों लेते हैं?


105

एक tupleअजगर में कम स्मृति अंतरिक्ष ले जाता है:

>>> a = (1,2,3)
>>> a.__sizeof__()
48

जबकि listस्मृति स्थान अधिक लगता है:

>>> b = [1,2,3]
>>> b.__sizeof__()
64

आंतरिक रूप से पायथन मेमोरी प्रबंधन पर क्या होता है?


1
मुझे यकीन नहीं है कि यह आंतरिक रूप से कैसे काम करता है, लेकिन सूची वस्तु में कम से कम अधिक कार्य हैं जैसे उदाहरण के लिए एपेंड जो ट्यूपल के पास नहीं है। इसलिए यह छोटे होने के लिए एक सरल प्रकार की वस्तु के रूप में टपल के लिए समझ में आता है
Metareven

मुझे लगता है कि यह मशीन से मशीन पर भी निर्भर करता है .... मेरे लिए जब मैं a = (1,2,3) को 72 और b = [1,2,3] को 88 पर लेता हूं।
अमृत

6
पायथन टुपल्स अपरिवर्तनीय हैं। परिवर्तनशील वस्तुओं में रनटाइम परिवर्तनों से निपटने के लिए अतिरिक्त ओवरहेड होता है।
ली डैनियल क्रोकर

@Metareven विधियों की संख्या एक प्रकार से स्मृति स्थान को प्रभावित नहीं करती है जो इंस्टेंस लेती हैं। विधि सूची और उनके कोड को ऑब्जेक्ट प्रोटोटाइप द्वारा नियंत्रित किया जाता है, लेकिन इंस्टेंस केवल डेटा और आंतरिक चर स्टोर करते हैं।
jjmontes

जवाबों:


144

मुझे लगता है कि आप सीपीथॉन का उपयोग कर रहे हैं और 64 बिट्स के साथ (मुझे अपने सीपीथॉन 2.7 64-बिट पर समान परिणाम मिला है)। अन्य पायथन कार्यान्वयन में अंतर हो सकता है या यदि आपके पास 32 बिट पायथन है।

कार्यान्वयन के बावजूद, lists परिवर्तनशील आकार के होते हैं जबकि tuples निश्चित आकार के होते हैं।

तो tupleएस सीधे तत्वों को संरचना के अंदर संग्रहीत कर सकता है, दूसरी ओर सूचियों को अप्रत्यक्ष की एक परत की आवश्यकता होती है (यह तत्वों के लिए एक संकेतक संग्रहीत करता है)। अप्रत्यक्ष की यह परत एक पॉइंटर है, 64 बिट सिस्टम पर, जो 64 बिट है, इसलिए 8bytes है।

लेकिन एक और बात यह है कि listवे करते हैं: वे अधिक-आवंटित करते हैं। अन्यथा हमेशाlist.append एक O(n)ऑपरेशन होगा - इसे परिशोधन करने के लिए (बहुत तेज !!!) इसे ओवर-आवंटित किया जाता है। लेकिन अब उसे आबंटित आकार और भरे हुए आकार पर नज़र रखनी होगी ( केवल एक आकार को संग्रहीत करने की आवश्यकता है, क्योंकि आवंटित और भरा हुआ आकार हमेशा समान है)। इसका मतलब है कि प्रत्येक सूची में एक और "आकार" संग्रहीत करना है जो 64 बिट सिस्टम पर एक 64 बिट पूर्णांक है, फिर से 8 बाइट्स।O(1)tuple

इसलिए lists की तुलना में कम से कम 16 बाइट्स अधिक मेमोरी की आवश्यकता होती है tuple। मैंने "कम से कम" क्यों कहा? अति-आवंटन के कारण। ओवर-आवंटन का मतलब है कि यह जरूरत से ज्यादा जगह आवंटित करता है। हालाँकि, ओवर-एलोकेशन की राशि "कैसे" पर निर्भर करती है कि आप सूची बनाएं और अपेंड / डिलीट हिस्ट्री:

>>> l = [1,2,3]
>>> l.__sizeof__()
64
>>> l.append(4)  # triggers re-allocation (with over-allocation), because the original list is full
>>> l.__sizeof__()
96

>>> l = []
>>> l.__sizeof__()
40
>>> l.append(1)  # re-allocation with over-allocation
>>> l.__sizeof__()
72
>>> l.append(2)  # no re-alloc
>>> l.append(3)  # no re-alloc
>>> l.__sizeof__()
72
>>> l.append(4)  # still has room, so no over-allocation needed (yet)
>>> l.__sizeof__()
72

इमेजिस

मैंने ऊपर स्पष्टीकरण के साथ कुछ चित्र बनाने का निर्णय लिया। शायद ये मददगार हों

यह है कि यह (योजनाबद्ध) आपके उदाहरण में स्मृति में संग्रहीत है। मैंने लाल (फ्री-हैंड) चक्रों के साथ अंतर पर प्रकाश डाला:

यहां छवि विवरण दर्ज करें

यह वास्तव में सिर्फ एक सन्निकटन है क्योंकि intऑब्जेक्ट्स भी पायथन ऑब्जेक्ट हैं और सीपीथॉन यहां तक ​​कि छोटे पूर्णांक का भी पुन: उपयोग करता है, इसलिए स्मृति में वस्तुओं का संभवतः अधिक सटीक प्रतिनिधित्व (हालांकि पठनीय नहीं है) होगा:

यहां छवि विवरण दर्ज करें

उपयोगी कड़ियाँ:

ध्यान दें कि __sizeof__वास्तव में "सही" आकार वापस नहीं करता है! यह केवल संग्रहीत मूल्यों का आकार लौटाता है। हालाँकि जब आप sys.getsizeofपरिणाम का उपयोग करते हैं तो वह अलग होता है:

>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72

24 "अतिरिक्त" बाइट्स हैं। ये वास्तविक हैं , यह कचरा कलेक्टर ओवरहेड है जिसका __sizeof__विधि में हिसाब नहीं है । ऐसा इसलिए है क्योंकि आप आमतौर पर सीधे तौर पर जादू के तरीकों का उपयोग नहीं करते हैं - उन कार्यों का उपयोग करें जो जानते हैं कि उन्हें कैसे संभालना है, इस मामले में: sys.getsizeof(जो वास्तव में जीसी ओवरहेड को मूल्य से वापस लौटाता है __sizeof__)।


" तो सूचियों को ट्यूपल्स की तुलना में कम से कम 16 बाइट्स की अधिक मेमोरी की आवश्यकता होती है। ", क्या यह 8 नहीं होगा? ट्यूपल्स के लिए एक आकार और सूचियों के लिए दो आकार का मतलब सूचियों के लिए एक अतिरिक्त आकार है।
ikegami

1
हां, सूची में एक अतिरिक्त "आकार" (8 बाइट) है, लेकिन एक पॉइंटर (8byte) को सीधे सीधे संरचना (उन्हें क्या करता है) में संग्रहीत करने के बजाय "PyObject" के सरणी में संग्रहीत करता है। 8 + 8 = 16।
MSeifert

2
मेमोरी एलोकेशन के बारे में एक अन्य उपयोगी लिंक stackoverflow.com/questions/40018398/…list
vishes_shell

@vishes_shell यह वास्तव में सवाल से संबंधित नहीं है क्योंकि प्रश्न में कोड बिल्कुल भी आवंटित नहीं करता है । लेकिन हाँ यह उस स्थिति में उपयोगी होता है जब आप list()किसी सूची बोध का उपयोग करते समय अधिक आवंटन की मात्रा के बारे में जानना चाहते हैं ।
MSeifert

1
@ user3349993 ट्यूपल्स अपरिवर्तनीय हैं, इसलिए आप एक ट्यूपल के लिए अपील नहीं कर सकते हैं या एक ट्यूपल से एक आइटम को हटा नहीं सकते हैं।
MSeifert

31

मैं CPython कोडबेस में एक गहरा गोता लगाऊंगा ताकि हम देख सकें कि वास्तव में आकारों की गणना कैसे की जाती है। आपके विशिष्ट उदाहरण में , कोई अति-आवंटन नहीं किया गया है, इसलिए मैं उस पर स्पर्श नहीं करूंगा

मैं यहाँ 64-बिट मान का उपयोग करने जा रहा हूँ, जैसे आप हैं।


listएस के लिए आकार की गणना निम्न फ़ंक्शन से की जाती है list_sizeof:

static PyObject *
list_sizeof(PyListObject *self)
{
    Py_ssize_t res;

    res = _PyObject_SIZE(Py_TYPE(self)) + self->allocated * sizeof(void*);
    return PyInt_FromSsize_t(res);
}

यहाँ Py_TYPE(self)एक मैक्रो है जो (लौटने ) ob_typeकी पकड़ लेता है जबकि एक अन्य मैक्रो है जो उस प्रकार से पकड़ लेता है। के रूप में गणना की जाती है जहां उदाहरण struct है।selfPyList_Type_PyObject_SIZEtp_basicsizetp_basicsizesizeof(PyListObject)PyListObject

PyListObjectसंरचना तीन क्षेत्रों है:

PyObject_VAR_HEAD     # 24 bytes 
PyObject **ob_item;   #  8 bytes
Py_ssize_t allocated; #  8 bytes

इन टिप्पणियों (जो मैंने छंटनी की) की व्याख्या करते हुए कि वे क्या हैं, उन्हें पढ़ने के लिए ऊपर दिए गए लिंक का पालन करें। PyObject_VAR_HEADतीन 8 बाइट फ़ील्ड ( और ob_refcount, ) में एक बाइट योगदान का विस्तार होता है।ob_typeob_size24

तो अभी के लिए resहै:

sizeof(PyListObject) + self->allocated * sizeof(void*)

या:

40 + self->allocated * sizeof(void*)

यदि सूची उदाहरण में ऐसे तत्व हैं जो आवंटित किए गए हैं। दूसरा भाग उनके योगदान की गणना करता है। self->allocated, जैसा कि नाम से ही स्पष्ट है, आवंटित तत्वों की संख्या रखती है।

किसी भी तत्व के बिना, सूचियों के आकार की गणना की जाती है:

>>> [].__sizeof__()
40

उदाहरण की संरचना का आकार।


tupleऑब्जेक्ट किसी tuple_sizeofफ़ंक्शन को परिभाषित नहीं करते हैं। इसके बजाय, वे object_sizeofअपने आकार की गणना करने के लिए उपयोग करते हैं :

static PyObject *
object_sizeof(PyObject *self, PyObject *args)
{
    Py_ssize_t res, isize;

    res = 0;
    isize = self->ob_type->tp_itemsize;
    if (isize > 0)
        res = Py_SIZE(self) * isize;
    res += self->ob_type->tp_basicsize;

    return PyInt_FromSsize_t(res);
}

यह, listएस के लिए के रूप में , पकड़ लेता है tp_basicsizeऔर, यदि ऑब्जेक्ट में एक शून्य-शून्य है tp_itemsize(जिसका अर्थ है कि यह चर-लंबाई के उदाहरण हैं), तो यह टपल में आइटमों की संख्या को गुणा करता है (जो इसके माध्यम से हो जाता है Py_SIZE) tp_itemsize

tp_basicsizeफिर से उपयोग करता है sizeof(PyTupleObject)जहां PyTupleObjectसंरचना शामिल है :

PyObject_VAR_HEAD       # 24 bytes 
PyObject *ob_item[1];   # 8  bytes

इसलिए, बिना किसी तत्व के (यानी, Py_SIZEरिटर्न 0) खाली टुपल्स का आकार इसके बराबर है sizeof(PyTupleObject):

>>> ().__sizeof__()
24

है ना? ठीक है, यहाँ कुछ अजीब जो मैं के लिए एक स्पष्टीकरण, नहीं मिला है है tp_basicsizeकी tupleरों वास्तव में गणना निम्न प्रकार है:

sizeof(PyTupleObject) - sizeof(PyObject *)

क्यों एक अतिरिक्त 8बाइट हटा दिया जाता tp_basicsizeहै कुछ ऐसा है जिसे मैं पता नहीं लगा पाया हूं। (संभावित विवरण के लिए MSeifert की टिप्पणी देखें)


लेकिन, यह मूल रूप से आपके विशिष्ट उदाहरण में अंतर हैlists भी आवंटित किए गए तत्वों की एक संख्या के आसपास रखता है जो यह निर्धारित करने में मदद करता है कि फिर से कब आवंटित किया जाए।

अब, जब अतिरिक्त तत्व जोड़े जाते हैं, तो सूचियाँ वास्तव में O (1) प्राप्त करने के लिए इस ओवर-आवंटन का प्रदर्शन करती हैं। इससे MSIifert के उत्तर में बड़े आकार के परिणाम मिलते हैं।


मेरा मानना ​​है ob_item[1]कि ज्यादातर एक प्लेसहोल्डर है (इसलिए यह समझ में आता है कि यह मूल से घटाया गया है)। का tupleउपयोग कर आवंटित किया गया है PyObject_NewVar। मैंने विवरण का पता नहीं लगाया है, इसलिए यह केवल एक शिक्षित अनुमान है ...
MSeifert

@MSeifert इसके लिए क्षमा करें, निश्चित :-) मैं वास्तव में नहीं जानता, मैं इसे किसी बिंदु पर अतीत में याद कर रहा हूं, लेकिन मैं इसे बहुत अधिक ध्यान नहीं दे रहा हूं, शायद मैं भविष्य में कुछ बिंदुओं पर एक सवाल
पूछूंगा

29

MSeifert जवाब इसे मोटे तौर पर कवर करता है; इसे सरल रखने के लिए आप सोच सकते हैं:

tupleअपरिवर्तनीय है। एक बार सेट होने के बाद, आप इसे बदल नहीं सकते। तो आप पहले से जानते हैं कि आपको उस ऑब्जेक्ट के लिए कितनी मेमोरी आवंटित करने की आवश्यकता है।

listपारस्परिक है। आप इसमें या इससे आइटम जोड़ या हटा सकते हैं। इसे इसका आकार (आंतरिक निहितार्थ के लिए) जानना होगा। यह आवश्यकतानुसार आकार देता है।

कोई मुफ्त भोजन नहीं है - ये क्षमताएं लागत के साथ आती हैं। इसलिए सूचियों के लिए स्मृति में ओवरहेड।


3

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

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