Wie bestimme ich die Größe eines Objekts in Python?
Die Antwort "Verwenden Sie einfach sys.getsizeof" ist keine vollständige Antwort.
Diese Antwort funktioniert direkt für integrierte Objekte, berücksichtigt jedoch nicht, was diese Objekte enthalten können, insbesondere welche Typen wie benutzerdefinierte Objekte, Tupel, Listen, Diktate und Mengen enthalten. Sie können sich gegenseitig Instanzen sowie Zahlen, Zeichenfolgen und andere Objekte enthalten.
Eine vollständigere Antwort
Unter Verwendung von 64-Bit-Python 3.6 aus der Anaconda-Distribution mit sys.getsizeof habe ich die Mindestgröße der folgenden Objekte festgelegt und festgestellt, dass Mengen und Diktate Speicherplatz vorab zuweisen, sodass leere Objekte erst nach einer festgelegten Menge wieder wachsen (was möglicherweise der Fall ist) variieren je nach Implementierung der Sprache):
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.
Wie interpretieren Sie das? Angenommen, Sie haben ein Set mit 10 Artikeln. Wenn jedes Element jeweils 100 Byte umfasst, wie groß ist die gesamte Datenstruktur? Der Satz ist 736 selbst, da er einmal auf 736 Bytes skaliert wurde. Dann addieren Sie die Größe der Elemente, sodass insgesamt 1736 Byte vorhanden sind
Einige Einschränkungen für Funktions- und Klassendefinitionen:
Beachten Sie, dass jede Klassendefinition eine Proxy- __dict__
Struktur (48 Byte) für Klassenattrs hat. Jeder Slot hat einen Deskriptor (wie a property
) in der Klassendefinition.
Geschlitzte Instanzen beginnen mit 48 Bytes in ihrem ersten Element und erhöhen sich um jeweils 8 Bytes. Nur leere Objekte mit Schlitz haben 16 Bytes, und eine Instanz ohne Daten macht wenig Sinn.
Außerdem enthält jede Funktionsdefinition Codeobjekte, möglicherweise Dokumentzeichenfolgen, und andere mögliche Attribute, sogar a __dict__
.
Beachten Sie auch, dass wir verwenden, sys.getsizeof()
weil wir uns um die marginale Speicherplatznutzung kümmern, einschließlich des Speicherbereinigungsaufwands für das Objekt aus den Dokumenten :
getizeof () ruft die __sizeof__
Methode des Objekts auf und fügt einen zusätzlichen Garbage Collector-Overhead hinzu, wenn das Objekt vom Garbage Collector verwaltet wird.
Beachten Sie auch, dass das Ändern der Größe von Listen (z. B. das wiederholte Anhängen an Listen) dazu führt, dass sie Speicherplatz vorab zuweisen, ähnlich wie bei Sätzen und Diktaten. Aus dem Quellcode 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);
Historische Daten
Python 2.7-Analyse, bestätigt mit guppy.hpy
und 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.
Beachten Sie, dass Wörterbücher ( aber keine Mengen ) in Python 3.6 eine kompaktere Darstellung erhalten
Ich denke, 8 Bytes pro zusätzlichem Referenzelement sind auf einem 64-Bit-Computer sehr sinnvoll. Diese 8 Bytes zeigen auf die Stelle im Speicher, an der sich das enthaltene Element befindet. Die 4 Bytes haben eine feste Breite für Unicode in Python 2, wenn ich mich richtig erinnere, aber in Python 3 wird str zu einem Unicode mit einer Breite, die der maximalen Breite der Zeichen entspricht.
(Weitere Informationen zu Slots finden Sie in dieser Antwort. )
Eine vollständigere Funktion
Wir wollen eine Funktion, die die Elemente in Listen, Tupeln, Mengen, Diktaten obj.__dict__
, und obj.__slots__
anderen Dingen durchsucht, an die wir vielleicht noch nicht gedacht haben.
Wir möchten gc.get_referents
uns bei dieser Suche darauf verlassen, dass sie auf C-Ebene funktioniert (was sie sehr schnell macht). Der Nachteil ist, dass get_referents redundante Mitglieder zurückgeben kann. Daher müssen wir sicherstellen, dass wir nicht doppelt zählen.
Klassen, Module und Funktionen sind Singletons - sie existieren einmal im Speicher. Wir sind nicht so an ihrer Größe interessiert, da wir nicht viel gegen sie tun können - sie sind Teil des Programms. Wir vermeiden es also, sie zu zählen, wenn auf sie verwiesen wird.
Wir werden eine schwarze Liste von Typen verwenden, damit wir nicht das gesamte Programm in unsere Größenanzahl einbeziehen.
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
Um dies mit der folgenden Whitelist-Funktion zu vergleichen, wissen die meisten Objekte, wie sie sich zum Zwecke der Speicherbereinigung selbst durchlaufen müssen (was ungefähr das ist, wonach wir suchen, wenn wir wissen möchten, wie teuer bestimmte Objekte im Speicher sind. Diese Funktionalität wird von verwendet gc.get_referents
.) Diese Maßnahme wird jedoch einen viel größeren Umfang haben, als wir beabsichtigt hatten, wenn wir nicht vorsichtig sind.
Zum Beispiel wissen Funktionen ziemlich viel über die Module, in denen sie erstellt werden.
Ein weiterer Kontrastpunkt ist, dass Zeichenfolgen, die Schlüssel in Wörterbüchern sind, normalerweise interniert werden, damit sie nicht dupliziert werden. Durch das Überprüfen auf id(key)
können wir auch vermeiden, dass Duplikate gezählt werden, was wir im nächsten Abschnitt tun. Die Blacklist-Lösung überspringt das Zählen von Schlüsseln, die insgesamt Zeichenfolgen sind.
Whitelisted-Typen, rekursiver Besucher (alte Implementierung)
Um die meisten dieser Typen selbst abzudecken, habe ich diese rekursive Funktion geschrieben, anstatt zu versuchen, die Größe der meisten Python-Objekte zu schätzen, einschließlich der meisten integrierten Funktionen, Typen im Sammlungsmodul und benutzerdefinierter Typen (mit und ohne Slots). .
Diese Art von Funktion bietet eine viel feinere Kontrolle über die Typen, die für die Speichernutzung gezählt werden sollen, birgt jedoch die Gefahr, dass Typen weggelassen werden:
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)
Und ich habe es eher beiläufig getestet (ich sollte es nicht testen):
>>> 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
Diese Implementierung gliedert sich in Klassendefinitionen und Funktionsdefinitionen, da wir nicht alle ihre Attribute verfolgen. Da sie jedoch nur einmal im Speicher für den Prozess vorhanden sein sollten, spielt ihre Größe keine große Rolle.