Speicherverwaltung schreiben: Problem bei assert und free.


  • Mod

    Enomine schrieb:

    Bei der Ausführung führt Zeile 8 "assert(m_size == 0xcdcd);" in MemoryTesterElement zu einer Ausnahme.
    Bisher habe ich noch nicht verstanden warum. Dies war vorgegebener Code vom Professor und da das Objekt hier erst erstellt wird dürfte der Inhalt von m_size ja random sein.

    Das ist undfiniertes Verhalten. Das assert verlässt sich darauf, dass sich der Inhalt des Speicherbereichs, in dem das jeweilige MemoryTesterElement lebt, seit der Allokation (dort wird der Bereich ja mit 0xcd überschrieben) nicht geändert hat und dass der Wert von m_size von diesem Inhalt abhängt, was aber nicht der Fall ist.

    Enomine schrieb:

    Mir stellt sich die Frage ob der new operator auch für zeile 71 und 97 in BucketSystem überschrieben ist.

    Dort wird placement-new verwendet.



  • Wenn Ihr Programm fehlerfrei und ohne assertations durchläuft haben Sie Ihre erste Speicherverwaltung geschrieben. Wenn Sie jetzt in der Datei BucketSystem in den beiden new Routinen jeweils die erste Zeile auskommentieren und die zweite Zeile einkommentieren, verwenden Sie für alle Speicheranforderungen wieder das Standard malloc. Auf meinem Rechner ist die Variante mit dem eigenen Bucket Memory Manager gut doppelt so schnell.

    Kann das hier jemand verifizieren? Für mich klingt es erstmal Zweifelhaft, dass ein eigener "new" operator eine Performance Vorteil von 100% bringt. Sonst muss ich da am Wochenende mal was rumprobieren.



  • camper schrieb:

    Enomine schrieb:

    Bei der Ausführung führt Zeile 8 "assert(m_size == 0xcdcd);" in MemoryTesterElement zu einer Ausnahme.
    Bisher habe ich noch nicht verstanden warum. Dies war vorgegebener Code vom Professor und da das Objekt hier erst erstellt wird dürfte der Inhalt von m_size ja random sein.

    Das ist undfiniertes Verhalten. Das assert verlässt sich darauf, dass sich der Inhalt des Speicherbereichs, in dem das jeweilige MemoryTesterElement lebt, seit der Allokation (dort wird der Bereich ja mit 0xcd überschrieben) nicht geändert hat und dass der Wert von m_size von diesem Inhalt abhängt, was aber nicht der Fall ist.

    Möchtest du mir damit sagen, dass der Speicherbereich, in welchem MemoryTesterElement lebt tatsächlich mit 0xcd überschrieben ist, jedoch m_size außerhalb liegt? Hört sich für mich irgendwie unlogisch an xD Könntest du das näher erklären? Siehst du hier einen Programmierfehler von mir? Kann mir so schlecht vorstellen, dass es ein Denkfehler meines Professors ist.

    camper schrieb:

    Enomine schrieb:

    Mir stellt sich die Frage ob der new operator auch für zeile 71 und 97 in BucketSystem überschrieben ist.

    Dort wird placement-new verwendet.

    Könntest du mir dies bitte erklären? Nach den Vorlesungsunterlagen, Seite 20 wird dabei hinter dem "new" direkt eine Klammer gesetzt. Dies ist in Zeile 71 und 97 gar nicht der Fall. Deswegen sah ich es als normales new an. Die Frage war ob nun schon das Überladene benutzt wird und er versucht über meinen Speichermanager zu gehen oder ob es es einfach vom Heap holt.

    Danke - Enomine



  • Schlangenmensch schrieb:

    [...] Auf meinem Rechner ist die Variante mit dem eigenen Bucket Memory Manager gut doppelt so schnell.

    Kann das hier jemand verifizieren? Für mich klingt es erstmal Zweifelhaft, dass ein eigener "new" operator eine Performance Vorteil von 100% bringt. Sonst muss ich da am Wochenende mal was rumprobieren.

    Darfst du gern. Kannst ja mal selbst die Aufgabe lösen xD Gern höre dir die Vorlesung mal an.

    Danke - Enomine


  • Mod

    Enomine schrieb:

    Möchtest du mir damit sagen, dass der Speicherbereich [...] mit 0xcd überschrieben ist, jedoch m_size außerhalb liegt?

    Nein.
    Bevor das MemoryTesterElement and der jeweiligen Speicherstelle entstanden ist, befindet sich dort nur roher Speicher (d.h. man kann man als Array aus (unsigned) char) betrachten). Wenn dann im Konstruktor auf den gespeicherten Wert an der Stelle m_size zugegriffen wird, wird also tatsächlich auf die gespeicherten einzelnen char-Werte zugegriffen, der Typ des Ausdrucks ist aber nicht (unsigned) char sondern unsigned short; und diese Art von Aliasing ist verboten.

    Ob das tatsächlich die Ursache für das Fehlschlagen des assert ist, kann ich nicht beurteilen. Der Author macht sich ohne Not von 32-bit Windows abhängig, obwohl das Programm eigentlich keinerlei OS-spezifische Funktionalitäten nutzt.



  • Enomine schrieb:

    Die Frage war ob nun schon das Überladene benutzt wird und er versucht über meinen Speichermanager zu gehen oder ob es es einfach vom Heap holt.

    Das könntest du z.B. mit einem Debugger recht einfach selbst heraus finden. Oder mit aussagekräftigen Debug Ausgaben 😉



  • Hey ihr zwei,

    wie würdet ihr denn nun vorgehen, um die Aufgabenstellung zu erfüllen. Da ich in knapp 3 Wochen Drittversuch in der Klausur habe, ist es mir wichtiger die Aufgabenstellung zu erfüllen und zu verifizieren, dass MEIN Code okay ist, anstatt über mögliche "bessere" Verhaltensweisen meines Professors beim Programmieren, zu sprechen.

    Findet ihr, dass meine Implementierungen von Bucket.cpp und BucketAdmin.cpp korrekt sind? Die Klassen waren bis auf die Methodenrümpfe und Kommentare leer.

    Welche konkreten Schritte muss ich denn nun unternehmen, um das Programm "fehlerfrei und ohne assertations" zu bekommen?

    Bezüglich des Debuggens: Ich habe schon etwas rumgerätzelt mit dem Debugger, kann aber noch nicht sagen ob ich den richtigen Einsprungspunkt gefunden habe. Denn es werden ja bereits arbeiten erledigt, bevor die main aufgerufen wird. So wird die Zeile 12 "BucketAdmin m_memoryManager;" ja ausgeführt bevor die main startet. So habe ich es jedenfalls verstanden.
    Wie kann ich den Debugger so starten, dass er bei der aller ersten Anweisung direkt stoppt?

    Ich verfüge über Teamviewer und Skype. Möchtet ihr mir in einer solchen Konferenz mal über die Schulter schauen?

    Danke - Enomine



  • Ich muss verstanden haben wie man sein eigenes Speicherverwaltungssystem aufbauen kann über die Bucket-Strategie. Seht ihr das anhand meiner Implementierungen gegeben? Würdet ihr die Implementierungen also korrekt ansehen? Was muss ich tun, damit die Implementierungen auch mit dem vorgegebenen Code meines Professors zusammen funktionieren?

    Danke - Enomine



  • Ich habe im Moment keinen eigenen Rechner, der über Skype oder Teamviewer verfügt. Das ist auch nicht unbedingt nötig, denke ich.

    Ansonsten, habe ich grade in diesem Thread wenig gelesen, was Verbesserungen bzgl. der Vorgabe betrifft.

    Tipp fürs Debugging:
    Breakepoint in dein new(), und den Callstack anschauen um zu prüfen von wo aus es aufgerufen wird.
    Wahlweise: Breakpoint in den else Teil von "PerformeSingleTest". Von da aus in den new rein "steppen".
    Wenn du den Constructor von BucketAdmin debuggen willst, setz ein Breakpoint da rein.

    Ich habe in meinen Anfängen viele Ausgaben benutzt um Sachen zu Debuggen.
    Zum Beispiel in dein new() ein std::cout << "In my new overload" << "\n";, dass ist nicht schön, aber kann einem unter Zeitdruck manchmal schneller helfen, als sich noch in die Welt der Debugger einzuarbeiten.

    Camper hat geschrieben, dass sich der Autor von 32-Bit Systemen abhängig macht. Mit was für Einstellungen und auf was für einem System kompilierst du?

    Sry, aber der Misch aus malloc, new, memset usw. ist nichts, wo ich beim drüber lesen zu 100% sagen kann, "ja das passt so", dafür gibt es einfach zu viele Fallstricke.

    Edit:
    Was mir grade auffällt, hier:

    void* result = m_memoryManager.RequestMemory(size); 
         // void* result = NULL; 
         if (!result) 
         {   
             result = malloc(size); 
             // We fill the memory with the test pattern here to comply 
             // with the default that all memory is initialized with 0xcd. 
             memset(result, 0xcd, size); 
         }
    

    memset wird nur aufgerufen, wenn result ein nullpointer ist und sonst beschreibst deinen Speicherbereich überhaupt nicht mit dem verlangten Muster.

    Edit2: Das scheinst du im Konstruktor von Bucket zu versuchen... Für dich zur Infor, bei mir schlägt die Assertion auch Fehl, mit dem Vergleich 5 == 52685

    Edit 3: Nicht 5, sondern 0 == 52685



  • Schlangenmensch schrieb:

    [...]Ansonsten, habe ich grade in diesem Thread wenig gelesen, was Verbesserungen bzgl. der Vorgabe betrifft.

    Das ist ja das Problem 😉 Ich weiß nicht wie ich die assertion weg bekomme 😉

    [...]Camper hat geschrieben, dass sich der Autor von 32-Bit Systemen abhängig macht. Mit was für Einstellungen und auf was für einem System kompilierst du?

    Windows 7 64 Bit. Visual Studio 2017. Windows SDK Version 10.0.15063.0

    Sry, aber der Misch aus malloc, new, memset usw. ist nichts, wo ich beim drüber lesen zu 100% sagen kann, "ja das passt so", dafür gibt es einfach zu viele Fallstricke.

    Ja die Frage war tatsächlich auch nicht als kurzes drübersehen gemeint 😉 Ich weiß dass ihr ja alle Freizeit hier verbringt.
    Jedenfalls hatte ich deswegen die Aufgabe auch direkt als Projekt hochgeladen, damit man selbst rumprobieren kann. Ich persönlich mag das nicht wenn ich jemanden in Java helfe und der kein Projekt zur Verfügung stellt sondern nur Code.
    Deswegen schlage ich dies vor:

    Schlangenmensch schrieb:

    [...]Sonst muss ich da am Wochenende mal was rumprobieren.

    😉

    memset wird nur aufgerufen, wenn result ein nullpointer ist und sonst beschreibst deinen Speicherbereich überhaupt nicht mit dem verlangten Muster.
    Edit2: Das scheinst du im Konstruktor von Bucket zu versuchen... Für dich zur Infor, bei mir schlägt die Assertion auch Fehl, mit dem Vergleich 5 == 52685

    Hatte versucht 2x darauf hinzuweisen, dass von mir nur Bucket.cpp und BucketAdmin.cpp programmiert wurden und der Rest vom Professor vorgegeben ist, dort also auch keine Änderungen zu machen sind.

    Die if-Abfrage funktioniert so, dass wenn Bucket.cpp und BucketAdmin.cpp noch nicht ausprogrammiert sind, dass diese NULL zurück geben und dann der standard-Windows-Speicherverwalter benutzt wird. Wenn Bucket.cpp und BucketAdmin.cpp korrekt ausprogrammiert sind, dann merkt der das an der Stelle und benutzt den gerade programmierten Speicherverwalter. Die Aussage ist es ja dann, dass der Eigene schneller Arbeitet, als der von Windows. In der Aufgabenstellung ist auch angegeben, dass man da die erste Zeile auskommentieren und die zweite einkommentieren soll, um den ursprungszustand wieder herzustellen.

    Danke - Enomine



  • Schlangenmensch schrieb:

    Edit 3: Nicht 5, sondern 0 == 52685

    0 ist niemals gleich 52685 😉

    Danke - Enomine



  • In BucketAdmin::RequestMemory erstellst du mit

    Bucket b = *m_buckets[i];
    

    Eine Kopie, bei der der Destruktur beim Verlassen aufgerufen wird. In ReleaseMemory ebenso.
    Das Projekt habe ich mir aber nicht runtergeladen, Speicherverwaltung ist Arbeit, noch dazu keine schöne...

    Bucket* b = m_buckets[i];
    

    Ist das gemeint?


  • Mod

    void* operator new(unsigned int size)
    

    Der Standard schreibt für den ersten Parameter zwingend std::size_t vor. Falls das Programm nicht für 32bit compiliert wird, wird diese zusätzliche Überladung möglicherweise still nicht genutzt.



  • Folgende Änderungen haben zu einer Lauffähigkeit geführt.

    m_numOfFreeChunks++;
    m_freeChunks[m_numOfFreeChunks] = offset / m_bucketGranularity;
    
    =>
    
    m_freeChunks[m_numOfFreeChunks++] = offset / (long)m_bucketGranularity;
    
    ---
    
    long offset = m_basePointer - pointer;
    
    =>
    
    long offset = ((unsigned char*)pointer - m_basePointer);
    
    ---
    
    for (int i = capacity - 1; i >= 0; --i) {
        m_freeChunks[i] = capacity - i;
    }
    
    =>
    
    for (unsigned short i = 0; i < m_numOfFreeChunks; ++i) {
    	m_freeChunks[i] = i;
    }
    
    ---
    
    Bucket b = *m_buckets[i];
    v = b.RequestMemory(capacity);
    
    =>
    
    v = m_buckets[i]->RequestMemory(capacity);
    
    ---
    
    Bucket b = *m_buckets[i];
    if (b.ReleaseMemory(pointer)) {
    
    =>
    
    if (m_buckets[i]->ReleaseMemory(pointer)) {
    

    Ich hoffe ich habe keine Änderungen vergessen.

    Ich habe leider nicht alle diese Änderungen verstanden, warum es vorher ein Problem war. Vorallem das mit dem . und dem ->. Da habe ich noch Probleme mit den * und & Operatoren. Möglicherweise kann mir das nochmal jemand veranschaulichen, warum ich hier nicht mit . arbeiten darf und wie * und & da reinspielen.

    Danke - Enomine



  • Dies ist lauffähig:

    BucketAdmin::BucketAdmin(void)
    {
    	// PLACE YOU IMPLEMENTATION HERE.
    	unsigned short capacity = BUCKET_CAPACITY;
    	for (int i = 0; i < NUM_OF_BUCKETS; i++) {
    		unsigned short granularity = m_bucketSizes[i];
    		m_baseMemory = (unsigned char*)malloc(sizeof(Bucket));
    		Bucket *b = new(m_baseMemory) Bucket(granularity, capacity);
    		m_buckets[i] = b;
    	}
    }
    

    Jedoch stellt sich mir die Frage ob da in Verbindung mit dem Destruktor (free(m_baseMemory)) ein Speicherleck entsteht dadurch, dass nur das letzte m_baseMemory noch bekannt ist und die davor nicht mehr. Ist es so besser?

    BucketAdmin::BucketAdmin(void)
    {
    	// PLACE YOU IMPLEMENTATION HERE.
    	unsigned short capacity = BUCKET_CAPACITY;
    	m_baseMemory = (unsigned char*)malloc(sizeof(Bucket)*NUM_OF_BUCKETS);
    	Bucket* newBucketP = (Bucket *)m_baseMemory;
    	for (int i = 0; i < NUM_OF_BUCKETS; i++) {
    		unsigned short granularity = m_bucketSizes[i];
    		Bucket *b = new(&newBucketP[i]) Bucket(granularity, capacity);
    		m_buckets[i] = b;
    	}
    }
    

    Danke - Enomine



  • Schlangenmensch schrieb:

    Wenn Ihr Programm fehlerfrei und ohne assertations durchläuft haben Sie Ihre erste Speicherverwaltung geschrieben. Wenn Sie jetzt in der Datei BucketSystem in den beiden new Routinen jeweils die erste Zeile auskommentieren und die zweite Zeile einkommentieren, verwenden Sie für alle Speicheranforderungen wieder das Standard malloc. Auf meinem Rechner ist die Variante mit dem eigenen Bucket Memory Manager gut doppelt so schnell.

    Kann das hier jemand verifizieren? Für mich klingt es erstmal Zweifelhaft, dass ein eigener "new" operator eine Performance Vorteil von 100% bringt. Sonst muss ich da am Wochenende mal was rumprobieren.

    Hey hab dir das aktuelle, laufende Projekt mal nochmal hochgeladen.
    http://www.share-online.biz/dl/GXKAM43PFJQ

    Habe mit einkommentierter erster Zeile 7 Sekunden.
    Habe mit einkommentierter zweiter Zeile 10 Sekunden.
    Also hier pie mal Daumen 30% Geschwindigkeitsverbesserung.

    Danke - Enomine



  • Hi, ich habe mal versucht ein paar Beispiele für *, &, -> usw. zu erstellen. Ich hoffe, ich habe nichts vergessen

    #include <iostream>
    class A
    {
    public:
       void doThis() { std::cout << "number of calls: " << a++ <<"\n";};
    
       int a = 0;
    };
    
    int main()
    {
       A* pointerToA = new A; //dynamisch erstellt. Pointer auf ein Objekt vom Typ A
       pointerToA->doThis();  // da Pointer, Aufruf mit ->
    
       (*pointerToA).doThis(); //Erst derefenziert, dann aufruf mit "." da dereferenziert.
    
       A copyOfderefencedPointer = *pointerToA; //Pointer wird dereferenziert und anschließend eine Kopie vom Objekt erstellt.
       copyOfderefencedPointer.doThis();
    
       delete pointerToA; //Obkelt auf das der Pointer Zeigt wird zerstört. Oben angelegte Kopie existiert weiter.
    
       A a; //Das, wie man es in C++ eigentlich immer haben will
       a.doThis();
    
       A* otherPointer = &a;   //Nur so lange gültig, wie a existiert
       otherPointer->doThis(); //Gleiches Ergebnis wie a.doThis();
    
       A& referenceToA = a;    //Referenz auf a. Alle Änderungen an referenceToA werden an a vorgenommen. im Ergebnis, dass selbe, wie oben mit otherPointer. Nur, dass refenceToA natürlich kein nullptr werden kann.
       referenceToA.doThis();
    }
    

    Was deine Zeitmessung betrifft, wie hast du denn gemessen und vor allem, mit welchen Optimierungsflags hast du kompiliert?

    Nachdem ich mir den Code gestern genauer angeschaut habe, kann ich mir gut vorstellen, dass er schneller läuft, als normaler dynamischer Speicher. Du erstellst halt ein Memory Pool, der am Anfang einmal alloziert wird. Um fair zu sein, müsstest du die Allozierung mit messen.



  • Hey Schlangenmensch,

    wenn ich in dem Projekt das Programm starte dann steht dort "Lokaler Windows Debugger" auf dem Button. In diesem Modus wird dann bei der Ausführung in Visual Studio eine Sekundenanzeige angezeigt im rechten Bereich. Mit der Speicherverwaltung endet das Programm pie mal Daumen bei 7 Sekunden (eine 6 oder 8 habe ich nie gesehen), ohne Speicherverwaltung bei 10 Sekunden (9 und 11 nie gesehen).

    Danke - Enomine



  • Geschwindigkeitsmessungen sind nur sinnvoll, wenn das Programm
    a) optimiert wurde (Release in VS)
    b) nicht im Debugger ausgeführt wird



  • Hi Enomine,

    Visual Studio kann verschiedene Konfigurationen für einzelne Projektemappen anlegen. Du hast oben irgendwo ein Drop Down Menü, bei dem du die aktive Konfiguration auswählen kannst. Wahrscheinlich steht bei dir da "Debug". Mit der Konfiguration, kannst du im Debugger durch die einzelnen Zeilen deines Programms durch gehen und dir überall anschauen, was für Werte in deinen Variablen steht.
    Die für die Ausführung interessantere Konfiguration ist "Release". Dort setzt Visual Studio Optimierungsflags. Wie genau und ob optimiert wird, kannst du in den Projekteinstellungen einstellen.
    Wenn du dein Projekt im Release Modus erstellst, wird das Ergebnis deutlich schneller sein. Du kannst das Ergebnis auch so ausführen und musst das nicht über den Debugger machen (einfach die exe starten).
    Auch Visual Studio kann dein Program ohne Debugger ausführen (Ctrl + F5).


Anmelden zum Antworten