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


155

나는이 목록을 작성 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 내부적 인 것입니까, 아니면 다른 설명입니까?


2
아마도 반복 연산자는 기본 배열의 크기를 정확하게 지정하는 함수를 호출 할 것입니다. 참고, 그 144 == sys.getsizeof([]) + 8*10)8 포인터의 크기입니다.
— juanpa.arrivillaga

1
참고 변경하는 경우 있음 10에 11의 [None] * 11목록 크기를 가지고 152있지만, 지능형리스트는 여전히 크기가 192. 이전에 연결된 질문은 정확히 중복되지 않지만 왜 이런 일이 발생하는지 이해하는 데 관련이 있습니다.
— Patrick Haugh

답변:


162

을 쓸 때 [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 일에 다시 할당해야했습니다.


1
그렇습니다. *앞에 크기를 알면 목록을 만드는 것이 좋습니다 .
— Andrej Kesely

27
@AndrejKesely 목록에서 [x] * n불변 x으로 만 사용 하십시오. 결과 목록은 동일한 객체에 대한 참조를 보유합니다.
— schwobaseggl

5
@ schwobaseggl 잘, 그것은 당신이 원하는 것 일지도 모르지만 그것을 이해하는 것이 좋습니다.
— juanpa.arrivillaga

19
@ juanpa.arrivillaga 사실 일 수도 있습니다. 그러나 일반적으로 그렇지 않으며 특히 SO는 왜 모든 데이터가 동시에 변경되었는지 궁금해하는 포스터로 가득합니다. :
— schwobaseggl

50

이 질문 에서 언급했듯이 목록 이해력은 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;. 나머지 함수는 단순히 배열을 채 웁니다.


"이 질문에서 언급했듯이 list-comprehension은 후드 아래에서 list.append를 사용합니다 .extend()." 라고 말하는 것이 더 정확하다고 생각합니다 .
— Accumulation

@Acccumulation 왜 그렇게 믿습니까?
— juanpa.arrivillaga

요소를 하나씩 추가하지 않기 때문입니다. 요소를 목록에 추가하면 실제로 새 메모리 할당으로 새 목록을 만들고 새 메모리 할당에 목록을 넣는 것입니다. 반면에,리스트 이해는 대부분의 새로운 요소를 이미 할당 된 메모리에 넣습니다. 그리고 할당 된 메모리가 부족할 때 새로운 요소에 충분하지 않은 다른 메모리 척도를 할당합니다.
— Accumulation

7
@Acccumulation 잘못되었습니다. list.append목록의 크기를 조정할 때 전체적으로 할당되므로 상각 된 상수 시간 작업입니다. 따라서 모든 추가 작업이 새로 할당 된 배열이되는 것은 아닙니다. 어쨌든 내가 링크 한 질문은 실제로 소스 코드에서 목록 이해 가 사용 하는 소스 코드를 보여줍니다 list.append. 잠시 후 랩톱으로 돌아와서 목록 이해 및 해당 LIST_APPENDopcode에 대한 분해 된 바이트 코드를 보여줄 수 있습니다
— juanpa.arrivillaga

3

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.

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


1
None기본 배열에 실제로 저장되어 있지는 않지만 저장되는 유일한 것은 PyObject포인터 (8 바이트)입니다. 모든 Python 객체는 힙에 할당됩니다. None단일 항목이므로 많은 목록이 None없는 것은 힙 의 동일한 객체에 대한 PyObject 포인터 배열을 만드는 것입니다 (추가 당 프로세스에서 추가 메모리를 사용하지 않음 None). "None에 미리 지정된 크기가 없습니다"라는 의미가 무엇인지 잘 모르겠지만 정확한 소리는 아닙니다. 마지막으로, getsizeof각 요소를 가진 루프 는 당신이 보여주고 있다고 생각하는 것을 보여주지 않습니다.
— juanpa.arrivillaga

말한대로 [없음] * 10의 크기는 [없음]의 크기와 같아야합니다. 그러나 분명히 그렇지 않습니다. 일부 추가 스토리지가 추가되었습니다. 실제로, 10 회 반복 된 (None)의 크기 (160)는 또한 [None]의 크기에 10을 곱한 것보다 작다. 지적했듯이 [None]에 대한 포인터의 크기는 [None] 자체의 크기보다 작습니다 (72 바이트가 아닌 16 바이트). 그러나 160 + 32는 192입니다. 위의 답변으로도 문제가 완전히 해결되지는 않습니다. 여분의 소량의 메모리 (아마도 머신 상태에 따라 다름)가 할당되어 있음이 분명합니다.
— StevenJD

"만약 당신이 말하는 것처럼, [없음] * 10의 크기는 [없음]의 크기와 같아야합니다." 다시 말하지만, 기본 버퍼가 과도하게 할당되었거나 목록의 크기에 기본 버퍼의 크기 (물론)가 포함되어 있다는 사실에 집중하고있는 것 같습니다. 이 질문. 다시 말하지만, 컨테이너 내부의 요소 크기를 고려하지 않기 때문에 gestsizeof각각 ele에 대한 사용 l2이 잘못되었습니다 . getsizeof(l2)
— juanpa.arrivillaga

마지막 주장을 스스로 증명하려면 l1 = [None]; l2 = [None]*100; l3 = [l2]다음을 수행하십시오 print(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3)). 다음과 같은 결과가 나타납니다 72 864 72. 즉, 각각 64 + 1*8, 64 + 100*8그리고 64 + 1*8다시 8 바이트 포인터 크기가 64 비트 시스템을 가정.
— juanpa.arrivillaga

1
앞서 언급했듯이 sys.getsizeof* 컨테이너의 항목 크기를 설명하지 않습니다. 로부터 문서 : "개체에 직접 기인 만 메모리 소비가 객체가 아닌 메모리 소비, 회계 그것은하려면 ... 참조 의미 재귀를 sizeof 재귀 적으로 컨테이너의 크기를 발견하고 () getsizeof 사용하는 예를 들어 요리법 "모든 내용."
— juanpa.arrivillaga
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.