Speicherplatz der Instanz einer Klasse

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Maschinenhans
User
Beiträge: 20
Registriert: Donnerstag 14. Juli 2011, 12:23

Hallo Forum.

Es gibt da bei mir ein Verstaendnisproblem, bei dem mir eventuell jemand auf die Spruenge helfen kann.

Ich wollte folgende Frage klaeren: "Braucht ein Objekt aus einer Klasse, das mit einem Satz Methoden ausgestattet ist mehr Speicher, als ein Objekt ohne diese Methoden? Die Ueberlegung ist fuer den Overhead und der daraus resultierende Resourcenverbrauch.

Um das zu beantworten habe ich das Skript geschrieben, das ich unten eingefuegt habe. Erwartet habe ich, dass das Dictionary ohne Methoden kleiner ist, als das mit allem Klimbim. Entweder mit viel oder mit wenig Unterschied. Da in meiner Welt im Kopf jedes Objekt aus der Klasse Store1 und Store2 zueinander einen kleinen Groessenunterschied aufweist, der in der Summe der Objekte gut sichtbar sein sollte. Der Compiler entweder zu jedem Objekt den ganzen Kram dazumacht, oder merkt, dass er das gleiche Objekt erstellt und die Methoden nur ein mal fuer alle Objekte hinterlegt.

Es gibt aber gar keinen Unterschied (das Ergebnis ist auf das byte genau gleich) und das wundert mich sehr.

Meine Fragen sind nun:
Warum gibt es hier keinen Unterschied im Speicherverbrauch?
Habe ich einen Denkfehler gemacht und der Test ist Fehlerhaft?
Oder ist das logisch und kann nur so sein, ich verstehe es nur nicht?

Vielen Dank fuer die Muehe den langen Beitrag zu lesen und verstehen zu wollen.
Maschinenhans

Code: Alles auswählen

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys

class Store1(object):
    '''Testklasse' 1''
    def __init__(self, x, y, z, x1, y1, z1):
        self.x, y, z, x1, y1, z1 = x, y, z, x1, y1, z1
        
    def getx(self):
        return self.x   
    def gety(self):
        return self.y
    def getz(self):
        return self.z
    def getx1(self):
        return self.x1   
    def gety1(self):
        return self.y1
    def getz1(self):
        return self.z1
    def setx(self, x):
        self.x = x
    def sety(self, y):
        self.y = y
    def setz(self, z):
        self.z = z
    def setx1(self, x):
        self.x1 = x
    def sety1(self, y):
        self.y1 = y
    def setz1(self, z):
        self.z1 = z

class Store2(object):
    '''Testklasse 2'''
    def __init__(self, x, y, z, x1, y1, z1):
        self.x, y, z, x1, y1, z1 = x, y, z, x1, y1, z1

def main():
    classes1 = {}
    classes2 = {}
    
    for i in range(10000):
        classes1[i] = Store1(1,2,3,4,5,6)
        classes2[i] = Store2(1,2,3,4,5,6)

    print('Groesse 1: {}'.format(sys.getsizeof(classes1)))
    print('Groesse 2: {}'.format(sys.getsizeof(classes2)))
    
if __name__ == '__main__':
    main()
Alles wird gut.
Benutzeravatar
snafu
User
Beiträge: 6854
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Die Doku zu der Funktion gibt einen entscheidenden Hinweis:
Only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to.
Es wird also nur die Liste und der Speicherplatz für die eigentlichen Verweise gezählt. Die dahinterstehenden Objekte werden nicht betrachtet. Somit bekommst du in beiden Fällen die selbe Größe geliefert, weil beide Listen die gleiche Anzahl an Objekten halten.

Wenn du das rekursiv machen willst, dann schau mal dort: https://code.activestate.com/recipes/577504/

EDIT: Wobei ich gerade sehe, dass dies für eigene Klassen nicht wie erwartet funktioniert, d.h. diese werden auch dabei nicht gesondert betrachtet.
Sirius3
User
Beiträge: 18260
Registriert: Sonntag 21. Oktober 2012, 17:20

Du gibst jeweils die Größe eines Wörterbuchs mit 10000 Einträgen aus. Die Größe jedes einzelnen Eintrags wird gar nicht mitgezählt. Und auch wenn, unterscheidet sich der Speicherverbrauch beider Instanzen nicht, weil der aus dem Verweis auf sechs Attribute und einem Verweis auf die Klassendefinition besteht. Der Speicherverbrauch der beiden Klassendefinitionen ist natürlich unterschiedlich, weil die eine 12 Verweise auf Methoden mehr hat. Die Antwort auf Deine ursprüngliche Frage lautet also Nein. Der Speicherverbrauch ist bis aufs Byte identisch.

Gibt es neben dieser akademischen Untersuchung noch einen Grund für deine Frage?
Maschinenhans
User
Beiträge: 20
Registriert: Donnerstag 14. Juli 2011, 12:23

snafu hat geschrieben: Freitag 13. März 2020, 05:38 Die Doku zu der Funktion gibt einen entscheidenden Hinweis:
Only the memory consumption directly attributed to the object is accounted for, not the memory consumption of objects it refers to.
Es wird also nur die Liste und der Speicherplatz für die eigentlichen Verweise gezählt. Die dahinterstehenden Objekte werden nicht betrachtet. Somit bekommst du in beiden Fällen die selbe Größe geliefert, weil beide Listen die gleiche Anzahl an Objekten halten.
...
Das ist mir durchgegangen. Vielen Dank fuer den Hinweis und damit ist ja auch der Test als Fehlerhaft nachgewiesen. Da muss ich mir wohl angewoehnen die Doku sorgfaeltiger zu lesen und zu verstehen.
Sirius3 hat geschrieben: Freitag 13. März 2020, 07:26 Du gibst jeweils die Größe eines Wörterbuchs mit 10000 Einträgen aus. Die Größe jedes einzelnen Eintrags wird gar nicht mitgezählt. Und auch wenn, unterscheidet sich der Speicherverbrauch beider Instanzen nicht, weil der aus dem Verweis auf sechs Attribute und einem Verweis auf die Klassendefinition besteht. Der Speicherverbrauch der beiden Klassendefinitionen ist natürlich unterschiedlich, weil die eine 12 Verweise auf Methoden mehr hat. Die Antwort auf Deine ursprüngliche Frage lautet also Nein. Der Speicherverbrauch ist bis aufs Byte identisch.

Gibt es neben dieser akademischen Untersuchung noch einen Grund für deine Frage?
Auch dir vielen Dank fuer deine Muehe. Das ist die Antwort, die ich haben wollte.

Ja. Es gibt noch einen anderen Grund fuer diese Frage:
Ich mochte es noch nie, wenn Programme oder Programmpakete mit den Resourcen aasen, als waeren Festplattenspeicher, RAM, Prozessorzeit und anderes ein unerschoepfliches Gut, das man sorglos und ungehemmt verbrauchen kann. Daher stelle ich mir beim schreiben meiner Skripte und kleinen Programme oft die Frage, ob meine Loesung in dieser Form sinnvoll ist, oder nicht. Ich moechte nicht der sein, der immer klagt das die anderen mit meinen Resourcen rumsauen, ich mache aber das gleiche. Das empfinde ich als sehr schlechten Stil und unschoenen Charakterzug.

In diesem konkreten Fall ist das jetzt eine Verarbeitung von Tracks in gpx-Dateien. Da diese Tracks (zum Reisen mit dem Motorrad) bei mir auch mal mehrere tausend km umfassen koennen, kommen natuerlich auch entsprechend viele Punkte (also auch gerne mindestens 5-stellige Anzahlen oder mehr) zusammen. Diese Punkte moecht ich wie dargestellt mittels einer Klasse in einem Dictionary speichern.

Da wollte ich verhindern, das die Methoden der Klassen in jeder Instanz mitgespeichert werden und damit einen immensen Overhead erzeugen, den ich dann zu vermeiden versuche. Das blinde Vertrauen in die Sprache war dann aber auch nicht meins. Ich wollte es zumindest kontrollieren.

Das war die Intention zu meiner Fragestellung.
Alles wird gut.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn das deine so grundlegenden Sorgen sind, dann ist Python an sich erstmal die falsche Wahl. Wenn du jeden Zyklus aus der Maschine quetschen willst, und mit moeglichst wenig RAM auskommen, dann lern einen low-levelige Sprache wie C, C++ oder Rust. Ggf. noch Go.

Falls aber die Abwaegung zwischen sehr viel mehr Zeit in deinen Code stecken vs den Computer einfach ein bisschen laenger rechnen lassen dann doch zugunsten von Python ausfaellt, dann kannst du zB durch den Einsatz von numpy (ggf. auch Pandas, aber das weiss ich nicht so genau) bei dazu geeigneten Daten und Algorithmen deutlich mehr aus der Maschine herausholen, ohne dabei auf den Komfort verzichten zu muessen. Aber das geht halt nicht fuer alles.
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Ich sehe ehrlich gesagt auch nicht, wie die von dir gezeigten Klassen ein gutes Mittel wären um vernünftig die Wegpunkte einer GPX-Datei darzustellen.
Und es gibt mit gpxpy ein gutes Modul um GPX-Dateien zu nutzen.
Maschinenhans
User
Beiträge: 20
Registriert: Donnerstag 14. Juli 2011, 12:23

Es geht mir nicht um Optimierung bis ins letzte und das Ausquetschen des letzten Rechenzyklus. Dann waere ich tatsaechlich nicht bei Python. Es geht um die generelle Vermeidung von Ressourcenfressern mit einfachen Mitteln und einfach ein bisschen ein Auge auf das ganze zu haben.

Die gezeigten Klassen waren Beispiele zur Veranschaulichung der Frage, nicht um Geodaten zu speichern und zu verarbeiten. Das Modul gpxpy kannte ich nicht und werde es mir mal ansehen. Danke fuer den Tipp.
Alles wird gut.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Faustregel bei sowas ist: Menschen haben nur eine erstaunlich schlechte Intuition, wo und was Speicher und Performance kostet, weshalb man also in der Regel profiled, um rauszufinden, wo man denn Flaschenhaelse hat. Wenn aber alles fluffig genug ist, dann muss man da auch nicht ran. Ja, gelegentlich kommt es dann zu einem groesseren Umbau. Aber das amortisiert sich in der gesparten Zeit, die man *nicht* damit verbracht hat, sich darueber zu viele Gedanken zu machen.
Benutzeravatar
__blackjack__
User
Beiträge: 14019
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Maschinenhans: In dem Code ist ein Syntaxfehler und in beiden Klassen wird auf den Exemplaren nur *ein* Datenattribut gesetzt (`x`). Das sieht mir nicht gewollt aus‽

Beschäftige Dich mal damit wie Listen und Wörterbücher implementiert sind und wie die mit dem Speicher umgehen. Und dann mach Dir klar das so gut wie jeder Namensraum in Python (Module, Klassen, Objekte allgemein) letztlich ein Wörterbuch ist. Da wirst Du dann vermutlich schreiend weglaufen. 😉

Sinnvollere Werte bekommt man durch das `tracamalloc`-Modul aus der Standardbibliothek mit dem man verfolgen kann wo wieviel Speicher angefordert wird. Im einfachsten Fall startet man das einfach vor einem Stück Code und schaut dann danach wie viel mehr Speicher danach verbraucht wird:

Code: Alles auswählen

#!/usr/bin/env python3
import sys
import tracemalloc


class Store1:
    def __init__(self, x, y, z, x1, y1, z1):
        self.x, self.y, self.z, self.x1, self.y1, self.z1 = x, y, z, x1, y1, z1

    def getx(self):
        return self.x

    def gety(self):
        return self.y

    def getz(self):
        return self.z

    def getx1(self):
        return self.x1

    def gety1(self):
        return self.y1

    def getz1(self):
        return self.z1

    def setx(self, x):
        self.x = x

    def sety(self, y):
        self.y = y

    def setz(self, z):
        self.z = z

    def setx1(self, x):
        self.x1 = x

    def sety1(self, y):
        self.y1 = y

    def setz1(self, z):
        self.z1 = z


class Store2:
    def __init__(self, x, y, z, x1, y1, z1):
        self.x, self.y, self.z, self.x1, self.y1, self.z1 = x, y, z, x1, y1, z1


def main():
    object_count = 10_000
    store_types = [Store1, Store2]
    results = dict()
    for store_type in store_types:
        tracemalloc.start()
        result = {i: store_type(1, 2, 3, 4, 5, 6) for i in range(object_count)}
        memory_size, _peak_memory_size = tracemalloc.get_traced_memory()
        tracemalloc.stop()
        print(
            f"{object_count} {store_type} = {memory_size},"
            f" {memory_size / object_count:.2f}/object"
        )
        results[store_type] = result

    for store_type, instances in results.items():
        print(f"getsizeof {store_type}: {sys.getsizeof(instances)}")


if __name__ == "__main__":
    main()
Ausgabe auf meinem Rechner ist:

Code: Alles auswählen

10000 <class '__main__.Store1'> = 2648652, 264.87/object
10000 <class '__main__.Store2'> = 2648628, 264.86/object
getsizeof <class '__main__.Store1'>: 295008
getsizeof <class '__main__.Store2'>: 295008
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Benutzeravatar
noisefloor
User
Beiträge: 4185
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

GPX ist ein XML-Format. Es gibt (performante) XML-Parser für Python, damit kannst du direkt in der GPX-Datei arbeiten. Was anderes wird gpxpy letztendlich auch nicht macht. Ich sehe eigentlich gar keinen Grund, dass erst in einen Python Datentyp umzukopieren.

Um warum wenn ein Dict? Gut, die sind inzwischen (ab Python 3.7) ja garantiert sortiert, aber vorher hättest du die Reihenfolge der Punkt allein durch die Datenstruktur verloren - was bei einem Track, der ja zwangsläufig linear ist, nicht wirklich Sinn macht.

Gruß, noisefloor
Maschinenhans
User
Beiträge: 20
Registriert: Donnerstag 14. Juli 2011, 12:23

__blackjack__ hat geschrieben: Freitag 13. März 2020, 16:53 @Maschinenhans: In dem Code ist ein Syntaxfehler und in beiden Klassen wird auf den Exemplaren nur *ein* Datenattribut gesetzt (`x`). Das sieht mir nicht gewollt aus‽ ...
Richtig! Das kommt von: "Da machste mal schnell ein Beispiel" :roll:
__blackjack__ hat geschrieben: Freitag 13. März 2020, 16:53 ...
Beschäftige Dich mal damit wie Listen und Wörterbücher implementiert sind und wie die mit dem Speicher umgehen. Und dann mach Dir klar das so gut wie jeder Namensraum in Python (Module, Klassen, Objekte allgemein) letztlich ein Wörterbuch ist. Da wirst Du dann vermutlich schreiend weglaufen. 😉 ...
Stelle niemals Fragen, deren Antwort du nicht hoeren moechtest? :lol:
__blackjack__ hat geschrieben: Freitag 13. März 2020, 16:53 ...
Sinnvollere Werte bekommt man durch das `tracamalloc`-Modul aus der Standardbibliothek mit dem man verfolgen kann wo wieviel Speicher angefordert wird. Im einfachsten Fall startet man das einfach vor einem Stück Code und schaut dann danach wie viel mehr Speicher danach verbraucht wird:
...
Ausgabe auf meinem Rechner ist:

Code: Alles auswählen

10000 <class '__main__.Store1'> = 2648652, 264.87/object
10000 <class '__main__.Store2'> = 2648628, 264.86/object
getsizeof <class '__main__.Store1'>: 295008
getsizeof <class '__main__.Store2'>: 295008
Von diesem Modul habe ich noch nie was gehoert. Das sieht spannend aus und ich werde mir das ansehen. Danke fuer deine Muehe.

Edit:
Ich sage mal, der Thread gilt als Geloest, ich finde nur keine Funktion, den Titel zu aendern.
Alles wird gut.
Antworten