A tuple
는 Python에서 메모리 공간을 덜 차지합니다.
>>> a = (1,2,3)
>>> a.__sizeof__()
48
반면 list
S 더 많은 메모리 공간을 취
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Python 메모리 관리에서 내부적으로 어떤 일이 발생합니까?
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__
).
list()
, 목록 이해를 사용할 때 초과 할당량에 대해 더 알고 싶은 경우 유용합니다 .
크기가 실제로 어떻게 계산되는지 볼 수 있도록 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의 답변에서 멋지게 다루기 때문에 크기가 더 커집니다.
ob_item[1]
대부분이 자리 표시 자라고 생각합니다 (따라서 기본 크기에서 빼는 것이 합리적입니다). 을 tuple
사용하여 할당됩니다 PyObject_NewVar
. 그 ... 그냥 추측 그래서 나는 세부 사항을 파악하지 않은
튜플의 크기는 접두사로 지정됩니다. 즉, 튜플 초기화시 인터프리터가 포함 된 데이터에 대해 충분한 공간을 할당하고 그게 끝이어서 변경 불가능 (수정할 수 없음)을 제공하는 반면 목록은 변경 가능한 객체이므로 동적을 의미합니다. 메모리 할당, 따라서 목록을 추가하거나 수정할 때마다 공간 할당을 피하기 위해 (변경된 데이터를 포함하고 데이터를 복사 할 수있는 충분한 공간을 할당) 향후 추가, 수정 등을 위해 추가 공간을 할당합니다. 요약하다.