Seite 1 von 1

[gelöst] Qt gibt Speicher nicht frei

Verfasst: 20. Oktober 2009 10:27
von gaucho
Hallo zusammen,

ich arbeite momentan an einem Programm, das eine größere Menge Daten halten und visualisieren soll. Da ich bei der Visualisierung zu Qt gegriffen habe, lag es nahe zur Datenhaltung die Klassen der QTL (QList, QMap, etc.) an Stelle der STL zu verwenden.

Leider bin ich dabei auf ein Problem gestoßen, was den Speicherverbrauch betrifft. Ich habe in meinem Programm eine Klasse (im Beispielcode "MyData"), welche als Attribut eine QStringList besitzt. Ein Container (im Beispielcode "MyDataContainer") hält eine Liste mit Zeigern auf MyData-Objekte. Diese Objekte sollen beim Löschen des Containers ebenfalls gelöscht werden. Erzeuge ich nun mit new eine Reihe von MyData-Objekten und packe sie in die Container-Klasse steigt logischerweise der Speicherverbrauch meine Programms. Nun sollte man aber annehmen, dass beim Löschen des Containers der allokierte Speicher wieder freigegeben wird, was aber nicht der Fall ist.

Der angehängt Beispielcode veranschaulicht das Problem. Ich erzeuge 1000000 MyData-Objekte (mit QStringList-Attribut), wobei hier bei mir der Speicherverbrauch auf ca. 20 MB steigt. Nach Freigabe des Containers müsste der Verbrauch wieder auf nahezu 0 MB abfallen. Stattdessen bleiben auf dem Heap aber weiterhin 16 MB! Da ich in meinem Programm weit größere Container habe als 20 MB, läuft mir so ziemlich schnell der Speicher voll, da eine Freigabe des Containers nahezu keine Wirkung hat. Was macht Qt also an dieser Stelle?

Ich habe gesehen, dass Qt bei QString, QList, QStringList und weiteren Klassen sogenanntes implizites Sharing betreibt (http://doc.trolltech.com/shared.html) aber selbst hierbei sollte ja Speicher, auf den nicht mehr referenziert wird, freigegeben werden. Momentan bin ich also ziemlich ratlos, insbesondere auch deshalb weil die Speicherfreigabe funktioniert, wenn ich den MyData* Zeiger anstatt in den Container zu packen, gleich wieder lösche (siehe Beispielcode Zeile 69 und 70).

Weiß hierzu jemand Rat? Bevor ich einen Bugreport schreibe, wäre es außerdem gut zu wissen, ob das Problem bei anderen ebenso auftritt.

Als kleine Info zum System:
Ich verwende Qt 4.5 unter Ubuntu 9.04 mit dem GCC:

Code: Alles auswählen

$g++ -v
Es werden eingebaute Spezifikationen verwendet.
Ziel: i486-linux-gnu
Konfiguriert mit: ../src/configure -v --with-pkgversion='Ubuntu 4.3.3-5ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --enable-targets=all --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
Thread-Modell: posix
gcc-Version 4.3.3 (Ubuntu 4.3.3-5ubuntu4)
Lösung:
http://qtforum.de/forum/viewtopic.php?p ... ght=#51138

Verfasst: 20. Oktober 2009 10:30
von pfid
Hab deinen Code nicht angeschaut, aber QList löscht nicht die Pointer in der Liste (wäre auch fatal). Deine MyData-Objekte musst du beim Löschen des Container-Objekts selbst aufräumen.

Verfasst: 20. Oktober 2009 10:36
von AuE
Stichwort shared pointer!

Es wäre ja auch Fatal->nehmen wir an du holst dir ne Liste von *QPushButtons.... und dann machst was mit und schmeisst die Liste wieder weg -> das heisst doch nicht das die Btn dann sinnlos sind oder?

Verfasst: 20. Oktober 2009 10:44
von gaucho
pfid hat geschrieben:Hab deinen Code nicht angeschaut, aber QList löscht nicht die Pointer in der Liste (wäre auch fatal). Deine MyData-Objekte musst du beim Löschen des Container-Objekts selbst aufräumen.
AuE hat geschrieben:Stichwort shared pointer!

Es wäre ja auch Fatal->nehmen wir an du holst dir ne Liste von *QPushButtons.... und dann machst was mit und schmeisst die Liste wieder weg -> das heisst doch nicht das die Btn dann sinnlos sind oder?
Die Pointer werden im Destruktor der Container-Klasse auch per delete wieder gelöscht. Im Beispielcode ist sogar ein Instanzzähler in der MyData-Klasse, der mir bestätigt, dass es nachher keine Instanzen mehr gibt. Somit werden also alle mit new allokierten Objekte per delete auch wieder gelöscht.

PS: Der Beispielcode hat knappe 90 Zeilen und sollte eigentlich leicht verständlich sein. Daher wäre es nett, wenn man vor dem Antworten zumindest mal einen Blick riskieren würde.

Verfasst: 20. Oktober 2009 10:52
von christophr
Bei mir funktioniert der Code so, wie er soll.

Versuch doch einmal, bei dir direkt im Anschluss an das Löschen des Containers noch einmal 10000 Objekte zu erzeugen. Steigt der Speicherverbrauch dann weiter an?
Normalerweise sollte der Speicherverbrauch nicht weiter ansteigen.

Hintergrund:
Je nach Compilerimplementation von new und delete wird nicht jedes mal Speicher vom Betriebssystem angefordert und zurückgegeben. Stattdessen fordert new jedes mal größere Blöcke an und verwaltet diesen Speicher selbst. Erst wenn es keinen Speicher mehr gibt, den new intern verwalten kann, wird neuer Speicher beim Betriebssystem angefordert. Im Gegenzug gibt delete den Speicher nicht ans Betriebssystem zurück, sondern stellt den frei gewordenen Speicher intern new wieder zur Verfügung. Solange noch interner Speicher frei ist, ist diese Variante um einiges schneller, als jedes Mal mit dem Betriebssystem zu kommunizieren.

Wenn dein Speicherverbrauch also in der zweiten Runde weiter ansteigt, liegt ein Fehler vor. Wenn nicht, ist alles normal. Das Betriebssystem erkennt, dass auf den Großteil des angeforderten Speichers nicht zugegriffen wird und sortiert ihn wieder in den Swap-Space zurück.


Zum Löschen der Pointer in einem Container gibt es übrigens die nette Funktion qDeleteAll(myList). Zu finden im QtAlgorithms-Header.


Gruß,
Christoph

Verfasst: 20. Oktober 2009 11:00
von RHBaum
Die Pointer werden im Destruktor der Container-Klasse auch per delete wieder gelöscht. Im Beispielcode ist sogar ein Instanzzähler in der MyData-Klasse, der mir bestätigt, dass es nachher keine Instanzen mehr gibt. Somit werden also alle mit new allokierten Objekte per delete auch wieder gelöscht.
Alles nur ne Kruecke. scheinbar funktioniert es doch nicht, weil das die QT den speicher ned wieder freigibt, halt ich fuer nen geruecht ^^ Aber nix ist sicher !

Wie misst du den Speicherverbrauch ???
wie christophr scho schreibt, die meisten C++ libs nutzen einen speichermanager (new direkt ans system zu schicken, iss eben eklig teuer). der vewaltet den speicher, fordert mehr an als wirklich gebraucht, gibt den ned sofort frei sondern meist erst wenn die aufraeumung nach dem main laeuft.

Um auf der sicheren seite zu bleiben, beherzige AuE's ratschlag. Speicher mit new zu erzeugen und in rohe Zeiger zu packen ist schon fast ne verfehlung unter C++. es gibt keinerlei gruende mehr das zu tun. Shared pointer und auto pointer und vectoren sind ein viel besseres mittel.
und meistens iss sogar ne umschiffung des news performanter.
mylist.insert(MyObject(x)); /// mylist vom Typ std::list<MyObject>
vs:
mylist.insert(new MyObject(x)); /// mylist vom Typ std::list<MyObject *>

da iss meistens 1. version immer noch performanter, trotz des temporaeren Objects aufn stack, gegenueber der 2. version ...
Vorrausgesetzt der CCTor von MyObject ist nahezu trivial, und macht kein ressourcenmanagment. Über exceptionsicherheit und Neutralitaet red ich gar ned erst ...

Ciao ...

Problem gelöst

Verfasst: 20. Oktober 2009 11:16
von gaucho
christophr hat geschrieben:Bei mir funktioniert der Code so, wie er soll.

Versuch doch einmal, bei dir direkt im Anschluss an das Löschen des Containers noch einmal 10000 Objekte zu erzeugen. Steigt der Speicherverbrauch dann weiter an?
Normalerweise sollte der Speicherverbrauch nicht weiter ansteigen.

Hintergrund:
Je nach Compilerimplementation von new und delete wird nicht jedes mal Speicher vom Betriebssystem angefordert und zurückgegeben. Stattdessen fordert new jedes mal größere Blöcke an und verwaltet diesen Speicher selbst. Erst wenn es keinen Speicher mehr gibt, den new intern verwalten kann, wird neuer Speicher beim Betriebssystem angefordert. Im Gegenzug gibt delete den Speicher nicht ans Betriebssystem zurück, sondern stellt den frei gewordenen Speicher intern new wieder zur Verfügung. Solange noch interner Speicher frei ist, ist diese Variante um einiges schneller, als jedes Mal mit dem Betriebssystem zu kommunizieren.

Wenn dein Speicherverbrauch also in der zweiten Runde weiter ansteigt, liegt ein Fehler vor. Wenn nicht, ist alles normal. Das Betriebssystem erkennt, dass auf den Großteil des angeforderten Speichers nicht zugegriffen wird und sortiert ihn wieder in den Swap-Space zurück.


Zum Löschen der Pointer in einem Container gibt es übrigens die nette Funktion qDeleteAll(myList). Zu finden im QtAlgorithms-Header.


Gruß,
Christoph
Hallo Christoph,

danke für die Antwort. Es scheint so zu sein, wie du sagst. Der Speicher bleibt dem Prozess erhalten und wird bei späterer Neu-Allokierung recycled. Ich habe das Beispielprogramm mal mit Hilfe der STL neu aufgesetzt (siehe Anhang), mit demselben Ergebnis. Somit scheint das Problem gelöst. Freut mich, dass es nicht an Qt liegt, weil der Aufwand sämtliche QTL-Typen durch STL zu ersetzen nicht ganz unerheblich gewesen wäre.

Eventuell werde ich sogar den Smart Pointer Ratschlag mal überdenken ;-)

Auf jeden Fall danke ich allen für die schnelle Hilfe.

Verfasst: 20. Oktober 2009 13:13
von pfid
gaucho hat geschrieben:
PS: Der Beispielcode hat knappe 90 Zeilen und sollte eigentlich leicht verständlich sein. Daher wäre es nett, wenn man vor dem Antworten zumindest mal einen Blick riskieren würde.
Bekenne mich schuldig :oops: