Speicher wird nicht mehr freigegeben ?!?!
-
Hallo!
Ich habe ein Problem mit einem Programm wo der Speicher nicht mehr freigegeben wird (50MB!!) Ich konnte das gleiche auch mit einem kleinen Testprogramm nachvollziehen und musste erkennen, dass dieses Problem nur unter Linux (wo ich den Service auch verwende) auftritt, jedoch nicht unter Windows.
#include <string> #include <vector> #include <iostream> using namespace std; #define LIST_COUNT 5 #define BUFFER_SIZE 1024 #define BUFFER_ADDS 10000 class Data { private: string name; public: Data() {} void Set(const char* _name) {name = _name;} }; class DataList { private: vector<Data> dataList; public: DataList() {} ~DataList() { Clear();} void Clear() { cout << "DataList::Clear()" << endl; dataList.clear(); } void Add(const char *_name) { Data dataobj; // Name setzen ---- if (_name) dataobj.Set(_name); // Element in Liste hinzfügen dataList.push_back (dataobj); } }; int main () { char buffer[BUFFER_SIZE+1]; memset(buffer, 'A', BUFFER_SIZE); buffer[BUFFER_SIZE] = '\0'; DataList datalist[LIST_COUNT]; for(int idx=0; idx<LIST_COUNT; idx++) { for(int i=0; i<BUFFER_ADDS; i++) datalist[idx].Add(buffer); } for(int idx=0; idx<LIST_COUNT; idx++) datalist[idx].Clear(); while(1) sleep(1); }
kurz zusammengefasst: Ich mache nichts anderes als jede menge Daten in eine Liste schaufeln und dann wieder freigeben (mir ist klar, dass man den vector vereinfachen hätte können, jedoch ist das hier die zusammengekürtzte Version meines großen Services usw.)
Leider bleiben jedoch in diesem Fall bei mir 50MB über und werden auch nicht mehr freigegeben, WARUM?
Wie schon erwähnt besteht das problem nur unter LINUX.DANKE für Eure Hilfe!
lg
-
Wo erwartest Du denn dass Memory freigegeben wird?
Welche Objekte werden denn nicht freigegeben?Edit:
Ein Problem was ich mir vorstellen könnte ist, dass bei clear() (auch bei resize()) auf den std::vector der Speicher nicht freigegeben wird. Er bleibt reserviert. Nur wenn der std::vector zerstört wird, wird auch dass unterliegende Memory freigegeben.
-
nach zeile 61 müsste wieder der ganze speicher freigegeben sein.
das problem ist, ja es werden alle objekte freigegeben, es wird überall der destruktor ausgeführt.aber danke für den hinweis mit resize, aber warum wird bei resize der speicher nicht zerstört und erst, wenn der vector zerstört wird?
habe nun alles mit new gelöst und da funktioniert es, weil ja der vector richtig zerstört wird
int main () { char buffer[BUFFER_SIZE+1]; memset(buffer, 'A', BUFFER_SIZE); buffer[BUFFER_SIZE] = '\0'; DataList *datalist[LIST_COUNT]; for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx] = new DataList(); for(int i=0; i<BUFFER_ADDS; i++) datalist[idx]->Add(buffer); } for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx]->Clear(); delete datalist[idx]; datalist[idx] = NULL; } while(1) sleep(1); }
nur kann mir jemand erklären warum das so ist? auch wenn ich mit resize, reserve, usw. alles versuche den vector zu zerstören (die capacity bleibt immer gleich)?
was ist, wenn ich den vector eigentlich nicht zerstören möchte und trotzdem dem speicher wieder freigeben möchte?lg
-
Trick17, um den vector wirklich auf minimale Groesse zurueckzuschrumpfen (resize() bzw. reserve() kann nicht verkleinern):
void Clear() { cout << "DataList::Clear()" << endl; vector<Data>().swap(dataList); }
Das erzeugt ein temporaeres vector-Objekt und vertauscht dessen Inhalt (naemlich nichts, nichtmal reservierter Speicher) mit dem Inhalt von datalist (dem ganzen Reservierten Speicher samt Inhalt). Das temporaere Objekt wird sofort wieder zerstoert und nimmt den ganzen Kram den du loswerden wolltest mit
-
Und es gibt wirklich nichts, dass den vector nur auf die benötigte Größe zurückbummst?
-
Badestrand schrieb:
Und es gibt wirklich nichts, dass den vector nur auf die benötigte Größe zurückbummst?
Doch:
vector<Data>(datalist).swap(dataList);
Der copy-Ctor kopiert den Inhalt, aber nicht den reservierten Speicher. Wenn also datalist Unmengen an reserviertem Speicher hat, wird im temporaeren vector nur soviel Speicher reserviert wie fuers Kopieren des Inhalts noetig ist. Nach dem swap hat datalist die kopie mit dem wenigen reservierten Speicher und der temporaere vector nimmt den Ueberfluessigen Speicher und das Original des Inhalts mit ins Jenseits.
-
danke pumuckl für den tipp, leider hat dies einen ganz komischen nebeneffekt:
die capacity schrumpft, ABER der belegete speicher bleibt trotzdem, wenn man das ganze macht ohne das der vector zerstört wird (delete version)!!!!
-
Nuja, dein Urspruengliches programm hat ja folgende Variablen:
char buffer[BUFFER_SIZE+1]; DataList datalist[LIST_COUNT];
Und verbraucht demnentsprechend deen folgenden Platz:
BUFFERSIZE+1 //(fuer den buffer) + LIST_COUNT * sizeof(DataList) + Reservierter Speicher fuer alle vectoren
mit dem swap/trick kriegst du den Verbrauch vom reservierten Speicher weg, aber die vector-"Ruempfe" (vermutlich ne Handvoll Pointer und n paar size_t's die speichern wie voll der vector ist) bleiben (und vom Speicherverbracuh ist ein DataList nicht mehr als ein vector). Mit der delete-version reduzierst du den Verbrauch auf
BUFFERSIZE+1 + LIST_COUNT * sizeof(DataList*)
Wobei die datalist-pointer weniger Platz verbrauchen duerften als die vectoren.
-
ups.. Vesehentlich geschrieben... Aber danke Pumuckl für
vector<Data>(datalist).swap(dataList);
!
-
meine neuen erkenntnnisse mit der swap-clear-lösung
- vector wird normal angelegt (stack) und nie gelöscht (weil kein blockende kommt, swap-clear funktioniert trotzdem (programm hat am ende 1,5MB)
- vector wird mit new angelegt (heap) und nicht mit delete gelöscht, swap-clear funktioniert nicht!!!! (programm hat am ende 50MB)
- vector wird mit new angelegt (heap) und mit delete am gelöscht, swap-clear funktioniert (programm hat am ende 1,5MB)
fazit: möchte man den vector weiterverwenden, dann muss man version 1 verwenden, weil sonst wird der speicher erst irgendwann freigegben??
- vector wird normal angelegt (stack) und nie gelöscht (weil kein blockende kommt, swap-clear funktioniert trotzdem (programm hat am ende 1,5MB)
-
Hi,
ich hinterfrage einfach mal die implizite Ausgangsthese, dass taff wirklich sehen kann, wann welcher Speicher "freigegeben" wird.
Wenn man sich nicht selbst einen komplette Speicherverwaltung schreibt, sondern nur "vom Betriebssystem aus" auf den Prozess schaut, sieht man das Ganze durch eine viel zu dicke Decke.
Dass eine Anwendung Speicher "deleted" bedeutet noch lange nicht, dass die Runtimelib dieses auch direkt dem Betriebssystem mitteilt.
Und:
Dass eine Runtimelib das Betriebssystem darüber informiert, dass es bestimmte Speicherblöcke nicht mehr braucht, bedeutet noch lange nicht, dass dieses sie für andere Anwendungen zur Verfügung stellt oder ein bestimmtes Anwenderprogramm (ps oder sonstwas) dieses als "frei" entdeckt.Deswegen sind so Aussagen wie "genau dann wird dieses freigegeben und jenes nicht" mit seeeehr viel Vorsicht zu genießen. Man kann einfach sehr viel falsch machen bei der Analyse der Speicherverwaltung...
Gruß,
Simon2.
-
@simon2
sollte es nicht so sein unter linux, dass wenn man ein objekt "zerstört" auch wirklich freigegeben wird? so viel ich weiß hat linux keinen garbage collector.
bzw. sieht man ja anhand meinem kleinem fazit, dass speicher ja durch tricks freigegeben wird und das sehe ich im top dann sofort oder in div. fällen nicht.@pumuckl
wie würdest du dann die swap-clear-speicherbereinigung machen, wenn du angenommen 1000 elemente in der klasse hast und davon 500 löscht - beim normalen erase sollten ja nichts freigegeben werden
-
taff schrieb:
@pumuckl
wie würdest du dann die swap-clear-speicherbereinigung machen, wenn du angenommen 1000 elemente in der klasse hast und davon 500 löscht - beim normalen erase sollten ja nichts freigegeben werdenSo wie ichs bei Badestrands Frage vorgeschlagen hab:
void DataList::ReservedToMinimum() { vector<Data>(dataList).swap(dataList); }
Wenn du nach deinen ganzen erase()s, die ja nur das Objekt loeschen, aber nicht den reservierten Speicher freigeben, ReservedToMinimum() ausfuerhst, endest du mit einem vector der den minimalen Platz reserviert hat, der fuer den inhalt moeglich ist.
-
sorry, da war ich wohl mit den gedanken nur bei meinen sachen.
aber eine sache habe ich bei der ganzen speicherreservierungspolitik von vectoren doch noch nicht durchschaut, weil wenn ich nun das ganze mit einem
vector<Data*> dataList;
mache, dann müsste die speicher belegung ja sich nur auf die größe des pointers beschränken und nicht auf den inhalt des pointers, oder?
jedenfalls bleiben mir nun wieder 50mb im memory stehenclass Data { public: string name; public: Data() {} virtual ~Data() {} void Set(const char* _name) {name = _name;} }; class DataListPointer { private: vector<Data*> dataList; public: DataListPointer() {} virtual ~DataListPointer() { Clear();} void Clear() { cout << "DataListPointer::Clear()" << endl; for(uint i=0; i< dataList.size(); i++) { delete dataList.at(i); dataList.at(i) = NULL; } dataList.clear(); vector<Data*>().swap(dataList); } void Add(const char *_name) { Data *dataobj(new Data()); // Name setzen ---- if (_name) dataobj->Set(_name); // Element in Liste hinzfügen dataList.push_back (dataobj); } }; int main () { char buffer[BUFFER_SIZE+1]; memset(buffer, 'A', BUFFER_SIZE); buffer[BUFFER_SIZE] = '\0'; DataListPointer *datalist[LIST_COUNT]; for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx] = new DataListPointer(); for(int i=0; i<BUFFER_ADDS; i++) datalist[idx]->Add(buffer); } for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx]->Clear(); delete datalist[idx]; datalist[idx] = NULL; } while(1) sleep(1); }
laut valgrind ist auch nichts belegt:
==31907== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 19 from 1)
==31907== malloc/free: in use at exit: 0 bytes in 0 blocks.
==31907== malloc/free: 2,050 allocs, 2,050 frees, 106,498,300 bytes allocated.ich versteh die c++ welt nicht mehr
-
taff schrieb:
@simon2
sollte es nicht so sein unter linux, dass wenn man ein objekt "zerstört" auch wirklich freigegeben wird? so viel ich weiß hat linux keinen garbage collector...Also Linux hat definitiv einen "Garbage Collector" (= eine Instanz, die Ressourcen aufräumt, die ein Programm "fallen lässt"). In C++ ist aber kein garbage collector für die Runtime vorge- oder beschrieben.
Unter "wirklich freigeben" versteht der Standard eben nur: "Das Betriebssystem darüber informieren, dass dieser Speicher nicht mehr benötigt wird". Nirgendwo wird festgelegt, was das Betriebssystem ("OS") mit dem Speicher anzustellen hat - und das ist auch gut so.
Deswegen kann das OS z.B. erstmal abwarten, ob das Programm nicht gleich wieder genausoviel Speicher wieder allozieren will. Damit spart es "Performance", weil der Speicher nicht erst dem "globalen Pool" bekanntgemacht werden muss UND weil der Speicher weniger fragmentiert wird. Oder ... oder ... oder ....
Dahinter stehen sehr komplexe Strategien.taff schrieb:
@simon2
...sieht man ja anhand meinem kleinem fazit, dass speicher ja durch tricks freigegeben wird und das sehe ich im top dann sofort oder in div. fällen nicht....Japp - aber WANN das der Fall ist, kannst Du nicht erzwingen, sondern liegt im Belieben des OS (außer, wenn Du einen Programmierfehler machst und den Speicher nicht freigibst).
Genau DAS beobachtest Du auch hiermit:
taff schrieb:
...
jedenfalls bleiben mir nun wieder 50mb im memory stehen
...
laut valgrind ist auch nichts belegt:
==31907== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 19 from 1)
==31907== malloc/free: in use at exit: 0 bytes in 0 blocks.
==31907== malloc/free: 2,050 allocs, 2,050 frees, 106,498,300 bytes allocated.ich versteh die c++ welt nicht mehr
... ich würde sagen: Du verstehst die Welt von OS-Speichermanagement nicht.
Valgrind sagt Dir, dass Dein Programm alles richtig gemacht hat, das sollte Dir reichen.Mal einfach anders herum (Annahme: Du schreibst ein OS): Warum sollte ein OS Zeit darin verschwenden, 50 MB wieder in den Pool zu werfen, wenn es noch 1,85 GB Speicher für andere Programme frei hat ?
IIRC hat selbst das 70er-Jahre-Unix Speicher erst dann wieder für andere Prozesse freigegeben, wenn der Gesamtspeicherbedarf knapp wurde...Gruß,
Simon2.
-
Ich würde im Code noch etwas abkürzen
void Clear() { cout << "DataListPointer::Clear()" << endl; for(uint i=0; i< dataList.size(); i++) { delete dataList.at(i); } vector<Data*>().swap(dataList); } void Add(const char *_name) { // Element in Liste hinzfügen dataList.push_back (new Data()); // Name setzen ---- if (_name) dataList.back()->Set(_name); }
-
Simon2 schrieb:
Unter "wirklich freigeben" versteht der Standard eben nur: "Das Betriebssystem darüber informieren, dass dieser Speicher nicht mehr benötigt wird". Nirgendwo wird festgelegt, was das Betriebssystem ("OS") mit dem Speicher anzustellen hat - und das ist auch gut so.
Deswegen kann das OS z.B. erstmal abwarten, ob das Programm nicht gleich wieder genausoviel Speicher wieder allozieren will. Damit spart es "Performance", weil der Speicher nicht erst dem "globalen Pool" bekanntgemacht werden muss UND weil der Speicher weniger fragmentiert wird. Oder ... oder ... oder ....
Dahinter stehen sehr komplexe Strategien.nur die frage ist, warum schaff ich eine lösung wo der speicher freigegeben wird
vector<Data> dataList;
und eine wo er nicht freigegeben wird?
vector<Data*> dataList;
Simon2 schrieb:
... ich würde sagen: Du verstehst die Welt von OS-Speichermanagement nicht.
Valgrind sagt Dir, dass Dein Programm alles richtig gemacht hat, das sollte Dir reichen.ähm das reicht leider eben nicht, weil dadurch habe ich einen zu hohen speicherverbrauch auf einem disklessserver, also so einfach ist die sache in meinen augen nicht, leider.
ich habe nun noch was weiteres rausgefunden
- es muss ganz stark darauf an kommen wo (stack/heap) ich die vector-dataelemente anlege, warum auch immer?
vector<Data> dataList; bzw. vector<Data*> dataList;
- so komisch es ausschaut, aber es ist auch ein unterschied wo ich den buffer anlege den ich ständig einem string zuweise, aber seht selbst:
in der folgenden version bleiben am ende bei mir 50MB über, obwohl alles richtig freigegeben wurde!!
int main () { char buffer[BUFFER_SIZE+1]; memset(buffer, 'A', BUFFER_SIZE); buffer[BUFFER_SIZE] = '\0'; DataListPointer *datalist[LIST_COUNT]; for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx] = new DataListPointer(); for(int i=0; i<BUFFER_ADDS; i++) datalist[idx]->Add(buffer); } for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx]->Clear(); delete datalist[idx]; datalist[idx] = NULL; } while(1) sleep(1); }
in dieser version wird auch noch der buffer per new angelegt und der speicher wird freigegeben!!!!!!
int main () { char *buffer(new char[BUFFER_SIZE+1]); memset(buffer, 'A', BUFFER_SIZE); buffer[BUFFER_SIZE] = '\0'; DataListPointer *datalist[LIST_COUNT]; for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx] = new DataListPointer(); for(int i=0; i<BUFFER_ADDS; i++) datalist[idx]->Add(buffer); } for(int idx=0; idx<LIST_COUNT; idx++) { datalist[idx]->Clear(); delete datalist[idx]; datalist[idx] = NULL; } delete buffer; while(1) sleep(1); }
merkwürdig oder?
-
Objekte die hinter einem Pointer versteckt sind bzw. auf dem Heap angelegt sind, muß man immer selber freigeben. Da ist nichts komisches dran.
By-Design!
Wenn du Polymorphie in einem Container haben willst, kannst du auch Smartpointer benutzen. Dann sorgt der Smartpointer dafür, aufzuräumen.
Und du brauchst uns auch nichts zeigen, weil wir das alles schon selber wissen. Der Unterschied zu Dir und uns ist: wir haben es verstanden, du nicht!
Deshalb finden wir es nicht merkwürdig. Weil
By-Design!
Wo ist also jetzt dein Problem?
-
Artchi schrieb:
Objekte die hinter einem Pointer versteckt sind bzw. auf dem Heap angelegt sind, muß man immer selber freigeben. Da ist nichts komisches dran.
By-Design!
MOMENT, wo gebe ich irgendwo keinen speicher frei? er wird überall freigegeben! nur laut top bleibt trotzdem speicher belegt!
Artchi schrieb:
Wenn du Polymorphie in einem Container haben willst, kannst du auch Smartpointer benutzen. Dann sorgt der Smartpointer dafür, aufzuräumen.
verwende ich einen smartpointer bleiben mir trotzdem 50MB im speicher, obwohl laut valgrind alle objekte zerstört wurden!
Artchi schrieb:
Wo ist also jetzt dein Problem?
das problem liegt darin, dass eben bei ALLEN designs der speicher IMMER von mir freigegeben wird und trotzdem hat dies bei div. designs keine auswirkung (laut top)
-
dann solltest du mal lernen die ausgaben von top und free zu interpretieren,
#free :
Mem: (total)2596496 (used)2280108 (free)316388
/+ buffers/cache: (used)724380 (free)1872116will sagen, das dir top einfach sagt, für wieviel speicher linux grade keine
Verwendung hat, und nicht wieviel wirklich frei ist,
bei der einen implementierung findet linux halt deinen speicher cache-wert, und
bei der anderen nicht, das sagt aber nicht, das du mehr speicher verbrauchstdas hat mit c++ nichts, aber auch wirklich garnichts zu tun, oder willst du
linux noch vorschreiben, wann es cachen//buffern darf, und wann nicht?nimm free und sieh dir die werte ohne buffers//caches an !!!