list ()는 list comprehension보다 약간 더 많은 메모리를 사용합니다.


79

그래서 나는 list물건 을 가지고 놀고 있었고 그것 list으로 만들어 지면 list()목록 이해보다 더 많은 메모리를 사용 한다는 이상한 점을 발견 했습니까? Python 3.5.2를 사용하고 있습니다.

In [1]: import sys
In [2]: a = list(range(100))
In [3]: sys.getsizeof(a)
Out[3]: 1008
In [4]: b = [i for i in range(100)]
In [5]: sys.getsizeof(b)
Out[5]: 912
In [6]: type(a) == type(b)
Out[6]: True
In [7]: a == b
Out[7]: True
In [8]: sys.getsizeof(list(b))
Out[8]: 1008

로부터 문서 :

목록은 여러 가지 방법으로 구성 될 수 있습니다.

  • 대괄호 쌍을 사용하여 빈 목록 표시 : []
  • 대괄호를 사용하여 항목을 쉼표로 구분 : [a],[a, b, c]
  • 목록 이해력 사용 : [x for x in iterable]
  • 유형 생성자 사용 : list()또는list(iterable)

그러나 그것을 사용 list()하면 더 많은 메모리 를 사용하는 것 같습니다 .

그리고 list많을수록 격차가 커집니다.

기억의 차이

왜 이런 일이 발생합니까?

업데이트 # 1

Python 3.6.0b2로 테스트합니다.

Python 3.6.0b2 (default, Oct 11 2016, 11:52:53) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(list(range(100)))
1008
>>> sys.getsizeof([i for i in range(100)])
912

업데이트 # 2

Python 2.7.12로 테스트 :

Python 2.7.12 (default, Jul  1 2016, 15:12:24) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(list(xrange(100)))
1016
>>> sys.getsizeof([i for i in xrange(100)])
920

3
매우 흥미로운 질문입니다. 파이썬 3.4.3에서 현상을 재현 할 수 있습니다. 더 흥미로운 점은 Python 2.7.5 sys.getsizeof(list(range(100)))에서 1016, getsizeof(range(100))872, getsizeof([i for i in range(100)])920입니다. 모두 유형이 list.
Sven Festersen

흥미로운 점은이 차이가 Python 2.7.10에도 있다는 것입니다 (실제 숫자는 Python 3과 다릅니다). 3.5와 3.6b에도 있습니다.
cdarke

Python 2.7.6에 대해 @SvenFestersen과 동일한 숫자를 얻습니다 xrange.
RemcoGerlich

2
여기에 가능한 설명이 있습니다 : stackoverflow.com/questions/7247298/size-of-list-in-memory . 방법 중 하나가를 사용하여 목록을 만드는 경우 append()메모리가 과도하게 할당되었을 수 있습니다. 이것을 명확히하는 유일한 방법은 파이썬 소스를 보는 것입니다.
Sven Festersen

10 % 만 더 추가됩니다 (어디서도 그렇게 말하지 않음). 제목을 "약간 더"로 바꾸겠습니다.
smci

답변:


61

나는 당신이 초과 할당 패턴을보고 있다고 생각합니다. 이것은 소스샘플입니다 .


길이가 0-88 인 목록 이해의 크기를 인쇄하면 패턴 일치를 볼 수 있습니다.

# create comprehensions for sizes 0-88
comprehensions = [sys.getsizeof([1 for _ in range(l)]) for l in range(90)]

# only take those that resulted in growth compared to previous length
steps = zip(comprehensions, comprehensions[1:])
growths = [x for x in list(enumerate(steps)) if x[1][0] != x[1][1]]

# print the results:
for growth in growths:
    print(growth)

결과 (형식 :) (list length, (old total size, new total size)):

(0, (64, 96)) 
(4, (96, 128))
(8, (128, 192))
(16, (192, 264))
(25, (264, 344))
(35, (344, 432))
(46, (432, 528))
(58, (528, 640))
(72, (640, 768))
(88, (768, 912))

초과 할당은 성능상의 이유로 수행되므로 증가 할 때마다 더 많은 메모리를 할당하지 않고도 목록이 증가 할 수 있습니다 (더 나은 분할 성능).

목록 이해력을 사용하는 것과 다른 가능한 이유는 목록 이해력이 생성 된 목록의 크기를 결정적으로 계산할 수는 없지만 계산할 list()수 있기 때문입니다. 즉, 최종적으로 채울 때까지 초과 할당을 사용하여 채울 때 이해력이 목록을 지속적으로 늘릴 것입니다.

일단 완료되면 사용되지 않은 할당 된 노드로 초과 할당 버퍼가 증가하지 않을 수 있습니다 (사실 대부분의 경우 초과 할당 목적을 무효화 할 것입니다).

list()그러나 최종 목록 크기를 미리 알고 있으므로 목록 크기에 관계없이 버퍼를 추가 할 수 있습니다.


소스의 또 다른 뒷받침 증거는을 호출하는 목록 내포를LIST_APPEND 볼 수 있다는 것 입니다 list.resize. 이는의 사용을 나타내며 , 이는 얼마나 많이 채워질 지 알지 못하면서 사전 할당 버퍼를 소비 함을 나타냅니다. 이것은 당신이보고있는 행동과 일치합니다.


결론적으로 list()목록 크기의 함수로 더 많은 노드를 미리 할당합니다.

>>> sys.getsizeof(list([1,2,3]))
60
>>> sys.getsizeof(list([1,2,3,4]))
64

List comprehension은 목록 크기를 알지 못하므로 커짐에 따라 추가 작업을 사용하여 사전 할당 버퍼를 고갈시킵니다.

# one item before filling pre-allocation buffer completely
>>> sys.getsizeof([i for i in [1,2,3]]) 
52
# fills pre-allocation buffer completely
# note that size did not change, we still have buffered unused nodes
>>> sys.getsizeof([i for i in [1,2,3,4]]) 
52
# grows pre-allocation buffer
>>> sys.getsizeof([i for i in [1,2,3,4,5]])
68

4
그러나 과잉 알로 카톤이 하나에서는 발생하지만 다른 하나에서는 발생하지 않는 이유는 무엇입니까?
cdarke

이것은 특히 list.resize. 나는 그가 소스를 탐색하는 데 전문가는 아니지만, 하나는 크기 조정을 호출하고 다른 하나는 그렇지 않으면 차이를 설명 할 수 있습니다.
Reut Sharabani

6
여기에 Python 3.5.2가 있습니다. 0에서 35까지의 목록 크기를 반복해서 인쇄 해보십시오. 목록을보고 64, 96, 104, 112, 120, 128, 136, 144, 160, 192, 200, 208, 216, 224, 232, 240, 256, 264, 272, 280, 288, 296, 304, 312, 328, 336, 344, 352, 360, 368, 376, 384, 400, 408, 416이해하기 위해 64, 96, 96, 96, 96, 128, 128, 128, 128, 192, 192, 192, 192, 192, 192, 192, 192, 264, 264, 264, 264, 264, 264, 264, 264, 264, 344, 344, 344, 344, 344, 344, 344, 344, 344. 특정 크기에 대해 더 많은 RAM을 사용하는 알고리즘으로 메모리를 미리 할당하는 것처럼 보이는 이해를 제외하고 싶습니다.
tavo

나는 똑같이 기대할 것이다. 곧 더 자세히 살펴볼 수 있습니다. 좋은 의견.
Reut Sharabani

4
실제로 list()목록 이해력이 할 수없는 목록 크기를 결정적으로 결정합니다. 이것은 목록 이해가 항상 목록의 "마지막"성장을 "유발"하지 않는다는 것을 의미합니다. 말이 되겠지.
Reut Sharabani

30

멋진 Python을 이해하도록 도와 주신 모든 분들께 감사드립니다.

그렇게 방대한 질문을하고 싶지 않고 (답을 올리는 이유) 내 생각을 보여주고 공유하고 싶어요.

마찬가지로 @ReutSharabani가 정확하게 기록 "리스트 () 결정 성리스트 크기를 결정한다." 그 그래프에서 볼 수 있습니다.

크기 그래프

append목록 이해력을 사용할 때 항상 특정 지점에 도달하면 확장되는 일종의 경계가 있습니다. 그리고 list()당신과 거의 같은 경계가 있지만 그들은 떠 있습니다.

최신 정보

@ReutSharabani , @tavo , @SvenFestersen 덕분에

요약하자면 : list()목록 크기에 따라 메모리를 미리 할당하고 목록 이해는이를 수행 할 수 없습니다 (예 : 필요할 때 더 많은 메모리를 요청합니다 .append()). 이것이 list()더 많은 메모리를 저장하는 이유 입니다.

list()메모리를 미리 할당 하는 그래프가 하나 더 있습니다. 따라서 녹색 선은 list(range(830))요소별로 추가되고 잠시 동안 메모리가 변경되지 않음을 보여줍니다 .

list ()는 메모리를 미리 할당합니다.

업데이트 2

@Barmar으로, 아래 설명에서 언급 list()내가 실행되도록 빠른 지능형리스트보다 더 저를해야한다 timeit()으로 number=1000의 길이 list에서 4**0까지 4**10그 결과는

시간 측정


1
빨간색 선이 파란색 위에있는 이유는 list생성자가 인수에서 새 목록의 크기를 결정할 수 있을 때 마지막 요소가 방금 도착했고 충분한 공간이없는 경우와 동일한 양의 공간을 미리 할당한다는 것입니다. 적어도 그것은 나에게 의미가 있습니다.
tavo

@tavo 그것은 나에게 똑같은 것 같습니다. 잠시 후 그래프에 표시하고 싶습니다.
vishes_shell

2
따라서 목록 이해는 메모리를 덜 사용하지만 발생하는 모든 크기 조정으로 인해 상당히 느릴 수 있습니다. 이들은 종종 목록 백본을 새 메모리 영역에 복사해야합니다.
Barmar

@ Barmar 실제로 나는 range객체로 시간 측정을 실행할 수 있습니다 (재미있을 수 있습니다).
vishes_shell

그리고 그것은 당신의 그래프를 더 예쁘게 만들 것입니다. :)
Barmar
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.