2012-12-07 6 views
12

PEP 412, in Python 3.3 implementiert, führt eine verbesserte Handhabung von Attributverzeichnissen ein, wodurch der Speicherbedarf von Klasseninstanzen effektiv reduziert wird. __slots__ wurde für den gleichen Zweck entwickelt, also gibt es keinen Sinn, __slots__ mehr zu verwenden?Macht PEP 412 __slots__ überflüssig?

In einem Versuch, mir die Antwort zu erfahren, ich folgende Test durchgeführt, aber die Ergebnisse machen nicht viel Sinn:

class Slots(object): 
    __slots__ = ['a', 'b', 'c', 'd', 'e'] 
    def __init__(self): 
     self.a = 1 
     self.b = 1 
     self.c = 1 
     self.d = 1 
     self.e = 1 

class NoSlots(object): 
    def __init__(self): 
     self.a = 1 
     self.b = 1 
     self.c = 1 
     self.d = 1 
     self.e = 1 

Python 3.3 Ergebnisse:

>>> sys.getsizeof([Slots() for i in range(1000)]) 
Out[1]: 9024 
>>> sys.getsizeof([NoSlots() for i in range(1000)]) 
Out[1]: 9024 

Python 2.7 Ergebnisse:

>>> sys.getsizeof([Slots() for i in range(1000)]) 
Out[1]: 4516 
>>> sys.getsizeof([NoSlots() for i in range(1000)]) 
Out[1]: 4516 

ich die Größe zumindest für Python erwartet hätte 2.7, zu unterscheiden, so gehe ich davon aus es i S stimmt etwas nicht mit dem Test überein.

+0

Haben Sie die Unterschiede in realen Situationen schon gemessen? :-) Außerdem kann '__slots__' für seine Nebeneffekte (ab) verwendet werden, wie zum Beispiel die Tatsache, dass willkürliche Attribute hinzugefügt werden. –

+0

Ja, ich kenne das Problem mit __slots__, es war mehr eine akademische Frage als in Bezug auf einen bestimmten Anwendungsfall. Ich habe versucht, ein paar Tests zu machen, aber habe keinen Unterschied zwischen Slots und nicht, in Python 3.3 oder 2.7 gefunden. Aber vielleicht ist mein Test fehlerhaft, also werde ich es auch posten. – aquavitae

Antwort

4

Nein, PEP 412 tut nicht machen __slots__ redundant.


Erstens hat Armin Rigo Recht, dass Sie es nicht richtig messen. Was Sie messen müssen, ist die Größe des Objekts plus die Werte plus die __dict__ selbst (nur für NoSlots) und die Schlüssel (nur für NoSlots).

Oder Sie könnten das tun, was er vorschlägt:

cls = Slots if len(sys.argv) > 1 else NoSlots 
def f(): 
    tracemalloc.start() 
    objs = [cls() for _ in range(100000)] 
    print(tracemalloc.get_traced_memory()) 
f() 

Wenn ich dies auf 64-Bit CPython 3.4 unter OS X laufen, bekomme ich 8824968 für NoSlots und 25624872 für Slots. Es sieht also so aus, als ob eine NoSlots Instanz 88 Bytes benötigt, während eine Slots Instanz 256 Bytes benötigt.


Wie ist das möglich?

Da gibt es immer noch zwei Unterschiede zwischen __slots__ und einem Schlüssel-Split __dict__.

Erstens werden die Hashtabellen, die von Wörterbüchern verwendet werden, unter 2/3 voll und sie wachsen exponentiell und haben eine minimale Größe, so dass Sie etwas mehr Platz haben. Und es ist nicht schwer herauszufinden, wie viel Platz man braucht, wenn man sich die gut kommentierte source anschaut: Sie werden 8 Hash-Buckets anstelle von 5 Slots-Zeigern haben.

Zweitens ist das Wörterbuch selbst nicht frei; Es hat einen Standard-Objektkopf, eine Anzahl und zwei Zeiger.Das klingt vielleicht nicht nach viel, aber wenn Sie über ein Objekt sprechen, das nur ein paar Attribute hat (beachten Sie, dass die meisten Objekte nur ein paar Attribute haben ...), kann der dict-Header so viel Unterschied wie die Hash-Tabelle machen .

Und natürlich in Ihrem Beispiel die Werte, so dass die einzigen Kosten hier das Objekt selbst ist, plus die 5 Slots oder 8 Hash-Buckets und dict-Header, so ist der Unterschied ziemlich dramatisch. Im wirklichen Leben, __slots__ wird selten , dass viel von Vorteil sein.


schließlich feststellen, dass PEP 412 nur behauptet:

Benchmarking zeigt, dass die Speichernutzung von 10% bis 20% für die objektorientierte Programme

darüber, wo Sie denken reduziert Verwenden Sie __slots__. Entweder sind die Einsparungen so groß, dass die Verwendung von __slots__ lächerlich wäre, oder Sie müssen wirklich die letzten 15% auspressen. Oder Sie erstellen ein ABC oder eine andere Klasse, von der Sie erwarten, dass sie von "what-knows-what" unterklassifiziert wird, und die Unterklassen benötigen möglicherweise die Einsparungen. In diesen Fällen wird die Tatsache, dass Sie die Hälfte des Nutzens ohne __slots__ oder sogar zwei Drittel des Nutzens erhalten, in den wenigsten Fällen ausreichen; Sie müssen immer noch __slots__ verwenden.

Der echte Gewinn ist in den Fällen, in denen es sich nicht lohnt, __slots__; Sie erhalten einen kleinen Vorteil kostenlos.

(Außerdem gibt es definitiv einige Programmierer, die die Hölle aus __slots__ überbeanspruchen, und vielleicht kann diese Änderung einige von ihnen überzeugen, ihre Energie in Mikro zu investieren, etwas anderes nicht ganz so irrelevant zu optimieren, wenn Sie Glück haben.)

+1

Sie haben die Speichergrößen für 'NoSlots'- und' Slots'-Instanzen angegeben, aber sind Sie sich der Reihenfolge sicher? Sollten Slots nicht leichter sein als NoSlots? Dies ist, was ich auf Win 7 64 Bits mit Python 3.4 erhalten. –

5

Das Problem ist sys.getsizeof(), die selten zurückgibt, was Sie erwarten. Zum Beispiel zählt es in diesem Fall die "Größe" eines Objekts ohne Berücksichtigung der Größe seiner __dict__. Ich schlage vor, dass Sie es erneut versuchen, indem Sie die tatsächliche Speichernutzung beim Erstellen von 100'000 Instanzen messen.

Beachten Sie auch, dass das Verhalten von Python 3.3 von PyPy inspiriert wurde, in dem __slots__ keinen Unterschied macht, also würde ich erwarten, dass es auch in Python 3.3 keinen Unterschied macht. Soweit ich das beurteilen kann, ist __slots__ fast nie von Nutzen.

+0

Ich habe gerade den vorgeschlagenen Test mit 64-Bit-Python 3.4 ausgeführt; Laut 'tracemalloc' ist' 'Slots() für _ im Bereich (100000)]' '8824968' zuzuweisen, während' NoSlots' '25624872' ist. Siehe [code] (http://pastebin.com/EF96k4na), um sicherzustellen, dass ich nichts Dummes getan habe. – abarnert

+0

Auch kann ich nicht sehen, wie es keinen Unterschied machen könnte. Es gibt immer noch die Schwäche, die Hashtabelle nur zu zwei Dritteln geladen zu halten (das könnte behoben werden, indem man ein indirektes indiziertes Array für die Werte verwendet, oder die Hashtabelle verdichtet, wenn die erste neue Kopie gesehen wird, oder verschiedene andere Tricks, aber keine davon) werden gemacht). Außerdem ist der 'dict'-Header selbst nicht frei - es kann nur ein kleiner konstanter Overhead sein, aber durch Vergleich mit einer Tabelle minimaler Größe und 5 Verweisen auf das kleine int '1' ist es der größte Teil des Objekts. – abarnert

+0

Ich habe die CPython 3.3-Implementierung nicht im Detail untersucht, daher kann ich Ihnen nicht sagen, warum es immer noch so einen großen Unterschied macht, '__slots__' zu verwenden. Alles was ich sicher sagen kann ist, dass '__slots__' in PyPy keinen Unterschied macht (sowohl PyPy2 als auch PyPy3). –

Verwandte Themen