Wie bestimme ich die Größe eines Objekts in Python?
Die Antwort: „Einfach verwenden sys.getsizeof
“, ist keine vollständige Antwort.
Diese Antwort tut direkt für eingebaute Objekte arbeiten, aber es berücksichtigt nicht, was diese Objekte enthalten können, insbesondere welche Typen, wie z. B. benutzerdefinierte Objekte, Tupel, Listen, Diktate und Mengen, enthalten sind. Sie können sowohl Instanzen als auch 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 bestimmt und bemerke, dass Sets und Diktate Speicherplatz vorbelegen, sodass leere nicht wieder wachsen, bis nach einer festgelegten Menge (die je nach Implementierung der Sprache variieren kann):
Python3:
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 100 Bytes groß ist, wie groß ist die gesamte Datenstruktur? Der Satz selbst ist 736, weil er einmal auf 736 Bytes vergrößert wurde. Dann addieren Sie die Größe der Elemente, also insgesamt 1736 Byte
Einige Einschränkungen für Funktions- und Klassendefinitionen:
Beachten Sie, dass jede Klassendefinition einen Proxy hat __dict__
(48 Byte) Struktur für Klassenattr. Jeder Slot hat einen Deskriptor (wie a property
) in der Klassendefinition.
Slot-Instanzen beginnen mit 48 Byte auf ihrem ersten Element und erhöhen sich um jeweils 8 Bytes. Nur leere Slotted-Objekte haben 16 Byte, und eine Instanz ohne Daten macht sehr wenig Sinn.
Außerdem hat jede Funktionsdefinition Codeobjekte, vielleicht Docstrings, 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, die den Garbage-Collection-Overhead für das Objekt beinhaltet, aus den Dokumenten:
getsizeof()
ruft das Objekt auf __sizeof__
-Methode 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. wiederholtes Anhängen an sie) dazu führt, dass sie Speicherplatz vorbelegen, ähnlich wie bei Sets und Diktaten. Von dem listobj.c-Quellcode:
/* 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 Sätze) hat ein kompaktere Darstellung in Python 3.6
Ich denke, 8 Bytes pro zusätzlichem Element, auf das verwiesen werden soll, 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 Spielautomaten finden Sie in dieser Antwort.
Eine vollständigere Funktion
Wir wollen eine Funktion, die die Elemente in Listen, Tupeln, Sets, Diktaten sucht, obj.__dict__
‘s, und obj.__slots__
sowie andere Dinge, an die wir vielleicht noch nicht gedacht haben.
Darauf wollen wir uns verlassen gc.get_referents
diese Suche durchzuführen, weil sie auf C-Ebene funktioniert (was sie sehr schnell macht). Der Nachteil ist, dass get_referents redundante Mitglieder zurückgeben kann, also müssen wir sicherstellen, dass wir nicht doppelt zählen.
Klassen, Module und Funktionen sind Singletons – sie existieren einmal im Speicher. Ihre Größe interessiert uns nicht so sehr, da können wir nicht viel dagegen tun – 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ößenzählung aufnehmen.
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 der folgenden Whitelist-Funktion gegenüberzustellen, wissen die meisten Objekte, wie sie sich zum Zwecke der Garbage Collection selbst durchlaufen (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
.) Allerdings wird diese Maßnahme weitaus umfangreicher ausfallen, als wir beabsichtigt haben, wenn wir nicht aufpassen.
Beispielsweise 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. Überprüfung auf id(key)
ermöglicht es uns auch, das Zählen von Duplikaten zu vermeiden, was wir im nächsten Abschnitt tun. Die Blacklist-Lösung überspringt das Zählen von Schlüsseln, die Zeichenfolgen sind, insgesamt.
Whitelist-Typen, rekursiver Besucher
Die meisten dieser Typen zu decken, anstatt sich auf die zu verlassen gc
-Modul habe ich diese rekursive Funktion geschrieben, um zu versuchen, die Größe der meisten Python-Objekte abzuschätzen, einschließlich der meisten Builtins, Typen im Collections-Modul und benutzerdefinierter Typen (slotted und andere).
Diese Art von Funktion bietet eine viel genauere Kontrolle über die Typen, die wir für die Speichernutzung zählen, birgt jedoch die Gefahr, dass wichtige Typen ausgelassen werden:
import sys
from numbers import Number
from collections import deque
from collections.abc import Set, Mapping
ZERO_DEPTH_BASES = (str, bytes, Number, range, bytearray)
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, 'items'):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, 'items')())
# 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 unittesten):
>>> 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 bricht auf Klassendefinitionen und Funktionsdefinitionen zusammen, weil wir nicht nach all ihren Attributen suchen, aber da sie für den Prozess nur einmal im Speicher existieren sollten, spielt ihre Größe wirklich keine allzu große Rolle.