code

튜플이 목록보다 메모리 공간을 덜 차지하는 이유는 무엇입니까?

codestyles 2020. 8. 16. 20:21
반응형

튜플이 목록보다 메모리 공간을 덜 차지하는 이유는 무엇입니까?


A tuple는 Python에서 메모리 공간을 덜 차지합니다.

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

반면 listS 더 많은 메모리 공간을 취

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

Python 메모리 관리에서 내부적으로 어떤 일이 발생합니까?


CPython과 64 비트를 사용하고 있다고 가정합니다 (CPython 2.7 64 비트에서 동일한 결과를 얻었습니다). 다른 Python 구현이나 32 비트 Python이있는 경우에는 차이가있을 수 있습니다.

구현에 관계없이 lists는 가변 크기이고 tuples는 고정 크기입니다.

따라서 tuples는 구조체 내부에 요소를 직접 저장할 수 있고, 반면에 목록에는 간접 레이어가 필요합니다 (요소에 대한 포인터를 저장합니다). 이 간접 계층은 64 비트 시스템에서 포인터이며, 따라서 8 바이트입니다.

하지만 또 다른 일 list이 있습니다. 그들은 과잉 할당합니다. 그렇지 않으면 항상 작업 list.append이 될 것입니다 .-상각 (훨씬 더 빠르게 !!!)하기 위해 과도하게 할당합니다. 그러나 이제는 할당 된 크기와 채워진 크기 추적해야합니다 ( 할당 된 크기 와 채워진 크기는 항상 동일하기 때문에 하나의 크기 만 저장하면됩니다). 즉, 각 목록은 64 비트 시스템에서 다시 8 바이트 인 64 비트 정수인 또 다른 "크기"를 저장해야합니다.O(n)O(1)tuple

따라서 lists는 tuples 보다 16 바이트 이상의 메모리가 필요합니다 . 내가 왜 "적어도"라고 말했습니까? 초과 할당 때문입니다. 초과 할당은 필요한 것보다 더 많은 공간을 할당 함을 의미합니다. 그러나 초과 할당량은 목록을 만드는 "방법"과 추가 / 삭제 기록에 따라 다릅니다.

>>> 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객체도 Python 객체이고 CPython은 작은 정수도 재사용 하기 때문에 실제로는 근사치 입니다. 따라서 메모리에있는 객체의 더 정확한 표현 (읽을 수는 없지만)은 다음과 같습니다.

여기에 이미지 설명 입력

유용한 링크:

Note that __sizeof__ doesn't really return the "correct" size! It only returns the size of the stored values. However when you use sys.getsizeof the result is different:

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

There are 24 "extra" bytes. These are real, that's the garbage collector overhead that isn't accounted for in the __sizeof__ method. That's because you're generally not supposed to use magic methods directly - use the functions that know how to handle them, in this case: sys.getsizeof (which actually adds the GC overhead to the value returned from __sizeof__).


I'll take a deeper dive into the CPython codebase so we can see how the sizes are actually calculated. In your specific example, no over-allocations have been performed, so I won't touch on that.

I'm going to use 64-bit values here, as you are.


The size for lists is calculated from the following function, 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);
}

Here Py_TYPE(self) is a macro that grabs the ob_type of self (returning PyList_Type) while _PyObject_SIZE is another macro that grabs tp_basicsize from that type. tp_basicsize is calculated as sizeof(PyListObject) where PyListObject is the instance struct.

The PyListObject structure has three fields:

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

these have comments (which I trimmed) explaining what they are, follow the link above to read them. PyObject_VAR_HEAD expands into three 8 byte fields (ob_refcount, ob_type and ob_size) so a 24 byte contribution.

So for now res is:

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

or:

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

If the list instance has elements that are allocated. the second part calculates their contribution. self->allocated, as it's name implies, holds the number of allocated elements.

Without any elements, the size of lists is calculated to be:

>>> [].__sizeof__()
40

i.e the size of the instance struct.


tuple objects don't define a tuple_sizeof function. Instead, they use object_sizeof to calculate their size:

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);
}

This, as for lists, grabs the tp_basicsize and, if the object has a non-zero tp_itemsize (meaning it has variable-length instances), it multiplies the number of items in the tuple (which it gets via Py_SIZE) with tp_itemsize.

tp_basicsize again uses sizeof(PyTupleObject) where the PyTupleObject struct contains:

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

So, without any elements (that is, Py_SIZE returns 0) the size of empty tuples is equal to sizeof(PyTupleObject):

>>> ().__sizeof__()
24

huh? Well, here's an oddity which I haven't found an explanation for, the tp_basicsize of tuples is actually calculated as follows:

sizeof(PyTupleObject) - sizeof(PyObject *)

why an additional 8 bytes is removed from tp_basicsize is something I haven't been able to find out. (See MSeifert's comment for a possible explanation)


But, this is basically the difference in your specific example. lists also keep around a number of allocated elements which helps determine when to over-allocate again.

Now, when additional elements are added, lists do indeed perform this over-allocation in order to achieve O(1) appends. This results in greater sizes as MSeifert's covers nicely in his answer.


MSeifert answer covers it broadly; to keep it simple you can think of:

tuple is immutable. Once it set, you can't change it. So you know in advance how much memory you need to allocate for that object.

list is mutable. You can add or remove items to or from it. It has to know the size of it (for internal impl.). It resizes as needed.

There are no free meals - these capabilities comes with a cost. Hence the overhead in memory for lists.


튜플의 크기는 접두사로 지정됩니다. 즉, 튜플 초기화시 인터프리터가 포함 된 데이터에 대해 충분한 공간을 할당하고 그게 끝이어서 변경 불가능 (수정할 수 없음)을 제공하는 반면, 목록은 변경 가능한 객체이므로 동적을 의미합니다. 메모리 할당, 따라서 목록을 추가하거나 수정할 때마다 공간 할당을 피하기 위해 (변경된 데이터를 포함하고 데이터를 복사 할 수있는 충분한 공간을 할당) 향후 추가, 수정 등을 위해 추가 공간을 할당합니다. 요약하다.

참고 URL : https://stackoverflow.com/questions/46664007/why-do-tuples-take-less-space-in-memory-than-lists

반응형