[python] 두 개의 동일한 목록에 다른 메모리 공간이있는 이유는 무엇입니까?

나는이 목록을 작성 l1하고 l2있지만, 다른 생성 방법과 각각 :

import sys

l1 = [None] * 10
l2 = [None for _ in range(10)]

print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))

그러나 결과는 나를 놀라게했습니다.

Size of l1 = 144
Size of l2 = 192

리스트 이해력으로 생성 된리스트는 메모리에서 더 큰 크기이지만, 그렇지 않으면 파이썬에서 두리스트는 동일합니다.

왜 그런 겁니까? 이것이 CPython 내부적 인 것입니까, 아니면 다른 설명입니까?



답변

을 쓸 때 [None] * 10Python은 정확히 10 개의 객체 목록이 필요하다는 것을 알고 있으므로 정확히 할당합니다.

리스트 이해를 사용할 때, 파이썬은 그것이 얼마나 필요한지 알지 못합니다. 따라서 요소가 추가됨에 따라 목록이 점차 커집니다. 각 재 할당마다 즉시 필요한 것보다 많은 공간을 할당하므로 각 요소에 대해 재 할당 할 필요가 없습니다. 결과 목록은 필요한 것보다 다소 클 수 있습니다.

비슷한 크기로 작성된 목록을 비교할 때이 동작을 볼 수 있습니다.

>>> sys.getsizeof([None]*15)
184
>>> sys.getsizeof([None]*16)
192
>>> sys.getsizeof([None for _ in range(15)])
192
>>> sys.getsizeof([None for _ in range(16)])
192
>>> sys.getsizeof([None for _ in range(17)])
264

첫 번째 방법은 필요한 것만 할당하고 두 번째 방법은 주기적으로 자라는 것을 알 수 있습니다. 이 예에서는 16 개의 요소에 충분히 할당되었으며 17 일에 다시 할당해야했습니다.


답변

이 질문 에서 언급했듯이 목록 이해력은 list.append후드에서 사용하므로 목록 크기 조정 메소드를 호출하여 전체적으로 할당합니다.

이것을 직접 보여주기 위해 실제로 disdissasembler를 사용할 수 있습니다 :

>>> code = compile('[x for x in iterable]', '', 'eval')
>>> import dis
>>> dis.dis(code)
  1           0 LOAD_CONST               0 (<code object <listcomp> at 0x10560b810, file "", line 1>)
              2 LOAD_CONST               1 ('<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_NAME                0 (iterable)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Disassembly of <code object <listcomp> at 0x10560b810, file "", line 1>:
  1           0 BUILD_LIST               0
              2 LOAD_FAST                0 (.0)
        >>    4 FOR_ITER                 8 (to 14)
              6 STORE_FAST               1 (x)
              8 LOAD_FAST                1 (x)
             10 LIST_APPEND              2
             12 JUMP_ABSOLUTE            4
        >>   14 RETURN_VALUE
>>>

코드 객체 LIST_APPEND의 디스 어셈블리 에서 opcode를 확인하십시오 <listcomp>. 로부터 문서 :

LIST_APPEND (i)

전화 list.append(TOS[-i], TOS). 목록 이해를 구현하는 데 사용됩니다.

이제리스트 반복 작업의 경우 다음을 고려할 때 진행중인 작업에 대한 힌트가 있습니다.

>>> import sys
>>> sys.getsizeof([])
64
>>> 8*10
80
>>> 64 + 80
144
>>> sys.getsizeof([None]*10)
144

따라서 크기 를 정확하게 할당 할 수있는 것 같습니다 . 소스 코드를 살펴보면 이것이 정확히 어떻게되는지 볼 수 있습니다.

static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n)
{
    Py_ssize_t i, j;
    Py_ssize_t size;
    PyListObject *np;
    PyObject **p, **items;
    PyObject *elem;
    if (n < 0)
        n = 0;
    if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
        return PyErr_NoMemory();
    size = Py_SIZE(a) * n;
    if (size == 0)
        return PyList_New(0);
    np = (PyListObject *) PyList_New(size);

즉, 여기에 : size = Py_SIZE(a) * n;. 나머지 함수는 단순히 배열을 채 웁니다.


답변

None은 메모리 블록이지만 미리 지정된 크기가 아닙니다. 또한 배열 요소 사이에 배열에 여분의 간격이 있습니다. 다음을 실행하여 직접 볼 수 있습니다.

for ele in l2:
    print(sys.getsizeof(ele))

>>>>16
16
16
16
16
16
16
16
16
16

총 크기는 l2가 아니라 오히려 적습니다.

print(sys.getsizeof([None]))
72

그리고 이것은의 크기의 10 분의 1보다 훨씬 큽니다 l1.

숫자는 운영 체제의 세부 사항과 운영 체제의 현재 메모리 사용량의 세부 사항에 따라 달라집니다. [없음]의 크기는 변수가 저장되도록 설정된 사용 가능한 인접 메모리보다 클 수 없으며 나중에 동적으로 더 크게 할당 된 경우 변수를 이동해야 할 수도 있습니다.


답변