튜플이 목록보다 메모리 공간을 덜 차지하는 이유는 무엇입니까?
A tuple
는 Python에서 메모리 공간을 덜 차지합니다.
>>> a = (1,2,3)
>>> a.__sizeof__()
48
반면 list
S 더 많은 메모리 공간을 취
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Python 메모리 관리에서 내부적으로 어떤 일이 발생합니까?
CPython과 64 비트를 사용하고 있다고 가정합니다 (CPython 2.7 64 비트에서 동일한 결과를 얻었습니다). 다른 Python 구현이나 32 비트 Python이있는 경우에는 차이가있을 수 있습니다.
구현에 관계없이 list
s는 가변 크기이고 tuple
s는 고정 크기입니다.
따라서 tuple
s는 구조체 내부에 요소를 직접 저장할 수 있고, 반면에 목록에는 간접 레이어가 필요합니다 (요소에 대한 포인터를 저장합니다). 이 간접 계층은 64 비트 시스템에서 포인터이며, 따라서 8 바이트입니다.
하지만 또 다른 일 list
이 있습니다. 그들은 과잉 할당합니다. 그렇지 않으면 항상 작업 list.append
이 될 것입니다 .-상각 (훨씬 더 빠르게 !!!)하기 위해 과도하게 할당합니다. 그러나 이제는 할당 된 크기와 채워진 크기 를 추적해야합니다 ( 할당 된 크기 와 채워진 크기는 항상 동일하기 때문에 하나의 크기 만 저장하면됩니다). 즉, 각 목록은 64 비트 시스템에서 다시 8 바이트 인 64 비트 정수인 또 다른 "크기"를 저장해야합니다.O(n)
O(1)
tuple
따라서 list
s는 tuple
s 보다 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은 작은 정수도 재사용 하기 때문에 실제로는 근사치 입니다. 따라서 메모리에있는 객체의 더 정확한 표현 (읽을 수는 없지만)은 다음과 같습니다.
유용한 링크:
tuple
Python 2.7 용 CPython 저장소의 구조체list
Python 2.7 용 CPython 저장소의 구조체int
Python 2.7 용 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 list
s 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 list
s, 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 tuple
s 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. list
s 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
'code' 카테고리의 다른 글
하나의 쿼리에서 MySQL 다중 조인? (0) | 2020.08.16 |
---|---|
msbuild를 사용하여 솔루션의 프로젝트 파일 지정 (0) | 2020.08.16 |
메타 클래스의 (구체적인) 사용 사례는 무엇입니까? (0) | 2020.08.16 |
EL에서 상수를 참조하는 방법은 무엇입니까? (0) | 2020.08.16 |
변경 유형별로 git diff 필터링 (0) | 2020.08.16 |