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 저장소의 구조체
참고 __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
(실제로 에서 반환 된 값에 GC 오버 헤드 를 추가합니다__sizeof__
).
답변
크기가 실제로 어떻게 계산되는지 볼 수 있도록 CPython 코드베이스에 대해 자세히 살펴 보겠습니다. 귀하의 특정 예 에서는 초과 할당이 수행되지 않았으므로 이에 대해서는 다루지 않겠습니다 .
여기서는 64 비트 값을 사용하겠습니다.
list
s 의 크기 는 다음 함수에서 계산됩니다 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
of self
(반환 PyList_Type
)를 가져 오는 _PyObject_SIZE
매크로 이고 tp_basicsize
해당 유형에서 가져 오는 또 다른 매크로입니다 . 인스턴스 구조체가 어디에 있는지 tp_basicsize
계산됩니다 .sizeof(PyListObject)
PyListObject
PyListObject
구조는 세 개의 필드가 있습니다 :
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
이것들은 그들이 무엇인지 설명하는 주석 (내가 다듬은)이 있습니다. 위 링크를 따라 읽으십시오. PyObject_VAR_HEAD
3 개의 8 바이트 필드 ( ob_refcount
, ob_type
및 ob_size
) 로 확장 되므로 24
바이트 기여도입니다.
그래서 지금 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
s tp_basicsize
와 마찬가지로 객체가 0이 아닌 경우 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
of tuple
s는 실제로 다음과 같이 계산됩니다.
sizeof(PyTupleObject) - sizeof(PyObject *)
추가 8
바이트가 제거되는 이유는 tp_basicsize
내가 알 수 없었던 것입니다. (가능한 설명은 MSeifert의 의견을 참조하십시오)
그러나 이것은 기본적으로 특정 예의 차이점 입니다 . list
s는 또한 할당 된 요소의 수를 유지하여 언제 다시 초과 할당 할지를 결정하는 데 도움이됩니다.
이제 추가 요소가 추가되면 목록은 실제로 O (1) 추가를 달성하기 위해이 초과 할당을 수행합니다. MSeifert의 답변에서 멋지게 다루기 때문에 크기가 더 커집니다.
답변
MSeifert 답변은 광범위하게 다루고 있습니다. 단순하게 유지하려면 다음을 생각할 수 있습니다.
tuple
불변입니다. 일단 설정되면 변경할 수 없습니다. 따라서 해당 객체에 할당해야하는 메모리 양을 미리 알고 있습니다.
list
변경 가능합니다. 여기에 항목을 추가하거나 제거 할 수 있습니다. 크기를 알아야합니다 (내부 impl.). 필요에 따라 크기가 조정됩니다.
무료 식사는 없습니다. 이러한 기능에는 비용이 따릅니다. 따라서 목록에 대한 메모리 오버 헤드.
답변
튜플의 크기는 접두사로 지정됩니다. 즉, 튜플 초기화시 인터프리터가 포함 된 데이터에 대해 충분한 공간을 할당하고 그게 끝이어서 변경 불가능 (수정할 수 없음)을 제공하는 반면 목록은 변경 가능한 객체이므로 동적을 의미합니다. 메모리 할당, 따라서 목록을 추가하거나 수정할 때마다 공간 할당을 피하기 위해 (변경된 데이터를 포함하고 데이터를 복사 할 수있는 충분한 공간을 할당) 향후 추가, 수정 등을 위해 추가 공간을 할당합니다. 요약하다.