IT story

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

hot-time 2020. 6. 15. 08:11
반응형

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


나는이 목록을 작성 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

So, it seems to be able to exactly allocate the size. Looking at the source code, we see this is exactly what happens:

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

Namely, here: size = Py_SIZE(a) * n;. The rest of the functions simply fills the array.


None is a block of memory, but it is not a pre-specified size. In addition to that, there is some extra spacing in an array between array elements. You can see this yourself by running:

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

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

Which does not total the size of l2, but rather is less.

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

And this is much greater than one tenth of the size of l1.

Your numbers should vary depending on both the details of your operating system and the details of current memory usage in your operating system. The size of [None] can never be bigger than the available adjacent memory where the variable is set to be stored, and the variable may have to be moved if it is later dynamically allocated to be larger.

참고URL : https://stackoverflow.com/questions/51526242/why-do-two-identical-lists-have-a-different-memory-footprint

반응형