Jak określić rozmiar obiektu w Pythonie?
Odpowiedź „Po prostu użyj sys.getsizeof” nie jest pełną odpowiedzią.
Że odpowiedź nie praca dla wbudowanego polecenia obiektów bezpośrednio, ale nie bierze pod uwagę tego, co te obiekty mogą zawierać, w szczególności, jakie typy obiektów, takich jak niestandardowe krotek, list dicts i zestawy zawierają. Mogą zawierać między sobą wystąpienia, a także liczby, ciągi znaków i inne obiekty.
Bardziej kompletna odpowiedź
Korzystając z 64-bitowego Pythona 3.6 z dystrybucji Anaconda, wraz z sys.getsizeof, określiłem minimalny rozmiar następujących obiektów i zauważam, że zestawy i dyktują wstępnie przydzielone miejsce, więc puste nie rosną, dopóki nie osiągną ustalonej ilości (co może zależy od implementacji języka):
Python 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.
Jak to interpretujesz? Powiedzmy, że masz zestaw z 10 przedmiotami. Jeśli każdy element ma po 100 bajtów, jak duża jest cała struktura danych? Sam zestaw to 736, ponieważ raz zwiększył rozmiar do 736 bajtów. Następnie dodajesz rozmiar elementów, więc w sumie jest to 1736 bajtów
Niektóre zastrzeżenia dotyczące definicji funkcji i klas:
Uwaga: każda definicja klasy ma strukturę proxy __dict__
(48 bajtów) dla attrów klas. Każdy slot ma deskryptor (jak a property
) w definicji klasy.
Instancje szczelinowe zaczynają się od 48 bajtów na pierwszym elemencie i zwiększają się o 8 każdego dodatkowego. Tylko puste obiekty szczelinowe mają 16 bajtów, a instancja bez danych ma bardzo mały sens.
Ponadto każda definicja funkcji ma obiekty kodu, może dokumenty i inne możliwe atrybuty, nawet a __dict__
.
Zauważ też, że używamy, sys.getsizeof()
ponieważ dbamy o wykorzystanie przestrzeni marginalnej, która obejmuje narzut związany z odśmiecaniem obiektu, z dokumentów :
getsizeof () wywołuje __sizeof__
metodę obiektu i dodaje dodatkowy narzut na śmieci, jeżeli obiekt jest zarządzany przez śmieciarz.
Zauważ też, że zmiana rozmiaru list (np. Powtarzające się dołączanie do nich) powoduje, że wstępnie przydzielają miejsce, podobnie jak zestawy i dykt. Z kodu źródłowego 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);
Dane historyczne
Analiza Python 2.7, potwierdzona za pomocą guppy.hpy
i 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.
Zauważ, że słowniki ( ale nie zestawy ) mają bardziej zwartą reprezentację w Pythonie 3.6
Myślę, że 8 bajtów na dodatkowy element do odniesienia ma sens na 64-bitowej maszynie. Te 8 bajtów wskazuje miejsce w pamięci, w którym znajduje się zawarty element. 4 bajty mają stałą szerokość dla Unicode w Pythonie 2, jeśli dobrze pamiętam, ale w Pythonie 3, str staje się Unicode o szerokości równej maksymalnej szerokości znaków.
(Więcej informacji na temat automatów znajdziesz w tej odpowiedzi )
Bardziej kompletna funkcja
Chcemy funkcji, która przeszukuje elementy na listach, krotkach, zestawach, słownikach obj.__dict__
i obj.__slots__
innych rzeczach, o których jeszcze nie myśleliśmy.
Chcemy polegać na gc.get_referents
tym wyszukiwaniu, ponieważ działa na poziomie C (dzięki czemu jest bardzo szybki). Minusem jest to, że get_referents może zwracać zbędnych członków, więc musimy upewnić się, że się nie liczymy.
Klasy, moduły i funkcje są singletonami - istnieją raz w pamięci. Nie interesuje nas ich rozmiar, ponieważ niewiele możemy z nimi zrobić - są częścią programu. Unikniemy ich liczenia, jeśli zdarzy się, że zostaną do nich odniesienia.
Użyjemy czarnej listy typów, więc nie uwzględniamy całego programu w naszym liczniku rozmiarów.
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
Aby to porównać z następującą funkcją z białej listy, większość obiektów wie, jak się przemieszczać w celu wyrzucania elementów bezużytecznych (co jest w przybliżeniu tym, czego szukamy, gdy chcemy wiedzieć, jak drogie są niektóre obiekty w pamięci. Z tej funkcji korzystają gc.get_referents
.) Jednak środek ten będzie miał znacznie szerszy zakres niż zamierzaliśmy, jeśli nie będziemy ostrożni.
Na przykład funkcje dużo wiedzą o modułach, w których są tworzone.
Innym punktem kontrastowym jest to, że ciągi będące kluczami w słownikach są zwykle internalizowane, więc nie są duplikowane. Sprawdzanie id(key)
pozwoli nam również uniknąć liczenia duplikatów, co zrobimy w następnej sekcji. Rozwiązanie czarnej listy całkowicie pomija liczenie kluczy, które są ciągami znaków.
Typy na białej liście, gość rekurencyjny (stara implementacja)
Aby pokryć większość z tych typów osobiście, zamiast polegać na module gc, napisałem tę funkcję rekurencyjną, aby spróbować oszacować rozmiar większości obiektów Pythona, w tym większości wbudowanych, typów w module kolekcji i typów niestandardowych (szczelinowych i innych) .
Ten rodzaj funkcji daje znacznie bardziej szczegółową kontrolę nad typami, które będziemy liczyć do użycia pamięci, ale grozi to pominięciem typów:
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)
I przetestowałem to raczej od niechcenia (powinienem to powtórzyć):
>>> 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
Ta implementacja dzieli się na definicje klas i definicji funkcji, ponieważ nie idziemy za wszystkimi ich atrybutami, ale ponieważ powinny one istnieć tylko raz w pamięci dla procesu, ich rozmiar naprawdę nie ma większego znaczenia.