파이썬에서 객체의 크기를 어떻게 결정합니까?
"sys.getsizeof 만 사용하십시오"라는 대답은 완전한 대답이 아닙니다.
이 답변 은 내장 객체에 직접 작동하지만 해당 객체에 포함 할 수있는 내용, 특히 사용자 정의 객체, 튜플, 목록, 받아쓰기 및 집합과 같은 유형을 설명하지는 않습니다. 숫자, 문자열 및 기타 객체뿐만 아니라 서로 인스턴스를 포함 할 수 있습니다.
더 완전한 답변
Anaconda 배포판의 64 비트 Python 3.6을 sys.getsizeof와 함께 사용하여 다음 객체의 최소 크기를 결정했으며 사전 할당 공간을 설정하고 지정하여 빈 공간이 설정된 양 이후까지 다시 커지지 않도록주의하십시오. 언어의 구현에 따라 다름) :
파이썬 3 :
Empty
Bytes type scaling notes
28 int +4 bytes about every 30 powers of 2
37 bytes +1 byte per additional byte
49 str +1-4 per additional character (depending on max width)
48 tuple +8 per additional item
64 list +8 for each additional
224 set 5th increases to 736; 21nd, 2272; 85th, 8416; 341, 32992
240 dict 6th increases to 368; 22nd, 1184; 43rd, 2280; 86th, 4704; 171st, 9320
136 func def does not include default args and other attrs
1056 class def no slots
56 class inst has a __dict__ attr, same scaling as dict above
888 class def with slots
16 __slots__ seems to store in mutable tuple-like structure
first slot grows to 48, and so on.
이것을 어떻게 해석합니까? 10 개의 아이템이 들어있는 세트가 있다고 가정 해 봅시다. 각 항목이 각각 100 바이트 인 경우 전체 데이터 구조는 얼마나됩니까? 한 번 크기가 736 바이트로 크기 때문에 세트 자체는 736입니다. 그런 다음 항목의 크기를 추가하여 총 1736 바이트입니다.
함수 및 클래스 정의에 대한 몇 가지주의 사항 :
각 클래스 정의에는 __dict__
클래스 attr에 대한 프록시 (48 바이트) 구조가 있습니다. 각 슬롯에는 property
클래스 정의에 설명자와 같은 설명자가 있습니다.
슬롯 형 인스턴스는 첫 번째 요소에서 48 바이트로 시작하여 추가 할 때마다 8 씩 증가합니다. 빈 슬롯 객체 만 16 바이트이며 데이터가없는 인스턴스는 거의 이해가되지 않습니다.
또한 각 함수 정의에는 코드 객체, 아마도 docstring 및 기타 가능한 속성이 __dict__
있습니다.
또한 우리 는 docs에서sys.getsizeof()
객체의 가비지 수집 오버 헤드를 포함하여 한계 공간 사용을 고려 하기 때문에 사용 합니다 .
getsizeof () __sizeof__
는 객체가 가비지 수집기에 의해 관리되는 경우 객체의 메서드를 호출하고 추가 가비지 수집기 오버 헤드를 추가합니다.
또한 목록 크기를 조정하면 (예 : 반복적으로 목록에 추가) 세트 및 받아쓰기와 마찬가지로 공간을 미리 할당합니다. 로부터 listobj.c 소스 코드 :
/* This over-allocates proportional to the list size, making room
* for additional growth. The over-allocation is mild, but is
* enough to give linear-time amortized behavior over a long
* sequence of appends() in the presence of a poorly-performing
* system realloc().
* The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
* Note: new_allocated won't overflow because the largest possible value
* is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
*/
new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);
역사적 자료
로 확인 파이썬 2.7 분석 guppy.hpy
및 sys.getsizeof
:
Bytes type empty + scaling notes
24 int NA
28 long NA
37 str + 1 byte per additional character
52 unicode + 4 bytes per additional character
56 tuple + 8 bytes per additional item
72 list + 32 for first, 8 for each additional
232 set sixth item increases to 744; 22nd, 2280; 86th, 8424
280 dict sixth item increases to 1048; 22nd, 3352; 86th, 12568 *
120 func def does not include default args and other attrs
64 class inst has a __dict__ attr, same scaling as dict above
16 __slots__ class with slots has no dict, seems to store in
mutable tuple-like structure.
904 class def has a proxy __dict__ structure for class attrs
104 old class makes sense, less stuff, has real dict though.
파이썬 3.6 에서는 사전 ( 그러나 세트는 아님)이 더 간결하게 표현 되었습니다.
추가 항목 당 8 바이트는 64 비트 시스템에서 많은 의미가 있다고 생각합니다. 이 8 바이트는 포함 된 항목이있는 메모리의 위치를 가리 킵니다. 4 바이트는 파이썬 2에서 유니 코드의 고정 너비입니다. 정확하게 기억한다면 파이썬 3에서는 str이 문자의 최대 너비와 동일한 너비의 유니 코드가됩니다.
(그리고 슬롯에 대한 자세한 내용은이 답변을 참조하십시오 )
보다 완전한 기능
우리는 목록, 튜플, 세트, 딕트 obj.__dict__
, 및 의 요소 obj.__slots__
뿐만 아니라 아직 생각하지 못한 다른 요소를 검색하는 함수를 원합니다 .
gc.get_referents
이 검색은 C 수준에서 작동하기 때문에이 검색 에 의존하고 싶습니다 (매우 빠름). 단점은 get_referents가 중복 멤버를 리턴 할 수 있으므로 두 배가되지 않도록해야합니다.
클래스, 모듈 및 함수는 싱글 톤이며 메모리에 한 번 존재합니다. 우리는 그들에 대해 할 수있는 일이 많지 않기 때문에 크기에 관심이 없습니다. 프로그램의 일부입니다. 따라서 참조가 발생하면 계산하지 않습니다.
블랙리스트 유형을 사용하여 전체 프로그램을 크기 수에 포함시키지 않습니다.
import sys
from types import ModuleType, FunctionType
from gc import get_referents
# Custom objects know their class.
# Function objects seem to know way too much, including modules.
# Exclude modules as well.
BLACKLIST = type, ModuleType, FunctionType
def getsize(obj):
"""sum size of object & members."""
if isinstance(obj, BLACKLIST):
raise TypeError('getsize() does not take argument of type: '+ str(type(obj)))
seen_ids = set()
size = 0
objects = [obj]
while objects:
need_referents = []
for obj in objects:
if not isinstance(obj, BLACKLIST) and id(obj) not in seen_ids:
seen_ids.add(id(obj))
size += sys.getsizeof(obj)
need_referents.append(obj)
objects = get_referents(*need_referents)
return size
이것을 다음의 화이트리스트 기능과 대조하기 위해, 대부분의 객체는 가비지 수집을 위해 자신을 순회하는 방법을 알고 있습니다. gc.get_referents
.) 그러나이 조치는 우리가주의하지 않으면 의도했던 것보다 훨씬 광범위 할 것입니다.
예를 들어, 함수는 작성된 모듈에 대해 많은 것을 알고 있습니다.
또 다른 대조적 인 점은 사전의 키인 문자열이 일반적으로 삽입되어 복제되지 않는다는 것입니다. 확인 id(key)
하면 중복 계산을 피할 수 있으며 다음 섹션에서 수행합니다. 블랙리스트 솔루션은 문자열 인 계산 키를 모두 건너 뜁니다.
허용 된 유형, 재귀 방문자 (이전 구현)
gc 모듈에 의존하는 대신 이러한 유형의 대부분을 직접 다루기 위해이 재귀 함수를 작성하여 대부분의 내장, 컬렉션 모듈의 유형 및 사용자 정의 유형 (슬롯 및 기타)을 포함한 대부분의 Python 객체의 크기를 추정하려고했습니다. .
이러한 종류의 함수는 메모리 사용량을 계산할 유형을 훨씬 세밀하게 제어하지만 유형을 생략 할 위험이 있습니다.
import sys
from numbers import Number
from collections import Set, Mapping, deque
try: # Python 2
zero_depth_bases = (basestring, Number, xrange, bytearray)
iteritems = 'iteritems'
except NameError: # Python 3
zero_depth_bases = (str, bytes, Number, range, bytearray)
iteritems = 'items'
def getsize(obj_0):
"""Recursively iterate to sum size of object & members."""
_seen_ids = set()
def inner(obj):
obj_id = id(obj)
if obj_id in _seen_ids:
return 0
_seen_ids.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, zero_depth_bases):
pass # bypass remaining control flow and return
elif isinstance(obj, (tuple, list, Set, deque)):
size += sum(inner(i) for i in obj)
elif isinstance(obj, Mapping) or hasattr(obj, iteritems):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, iteritems)())
# Check for custom object instances - may subclass above too
if hasattr(obj, '__dict__'):
size += inner(vars(obj))
if hasattr(obj, '__slots__'): # can have __slots__ with __dict__
size += sum(inner(getattr(obj, s)) for s in obj.__slots__ if hasattr(obj, s))
return size
return inner(obj_0)
그리고 나는 그것을 우연히 테스트했습니다 (단일 테스트해야합니다).
>>> getsize(['a', tuple('bcd'), Foo()])
344
>>> getsize(Foo())
16
>>> getsize(tuple('bcd'))
194
>>> getsize(['a', tuple('bcd'), Foo(), {'foo': 'bar', 'baz': 'bar'}])
752
>>> getsize({'foo': 'bar', 'baz': 'bar'})
400
>>> getsize({})
280
>>> getsize({'foo':'bar'})
360
>>> getsize('foo')
40
>>> class Bar():
... def baz():
... pass
>>> getsize(Bar())
352
>>> getsize(Bar().__dict__)
280
>>> sys.getsizeof(Bar())
72
>>> getsize(Bar.__dict__)
872
>>> sys.getsizeof(Bar.__dict__)
280
이 구현은 모든 속성을 따르지 않기 때문에 클래스 정의와 함수 정의에 대해 세분화되지만 프로세스의 메모리에 한 번만 존재해야하므로 크기는 실제로 중요하지 않습니다.