Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

04.08.2013, 19:57

C++ Memory Management

Hallo liebe Community,

ich habe heute einen kleinen Stack Allocator geschrieben, den ich unter Umständen in meinen Spielen nutzen möchte. Funktionieren tut er, aber ich weiß nicht, ob der von mir benutzte Code nicht irgendwelche Schwachstellen, bzw. unschöne Lösungen benutzt. Daher wollte ich euch bitten, mir zu sagen, ob ihr Verbesserungsvrschläge kennt.
Als Zweites wollte ich noch wissen, ob ihr diverse Quellen kennt, in welchen MemoryManagement näher beleuchtet wird. Die Theorie kann ich dank zahlreicher Bücher, doch in der Praxis weiß ich nicht, wie ich dieses Wissen umsetze (Für mich ist Memory an sich, das schwerste an einer Engine), daher wäre ich euch dankbar, wenn ihr mir Hinweise oder Quellen zu diesem Thema geben könntet.

Hier nun der Code meines StackAllocators:

Stack.hpp:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#pragma once

#ifndef STACK
#define STACK

#include <cstdint>

class Stack
{
public:
    explicit Stack(unsigned int bytes);
    ~Stack();

    void* Alloc(unsigned int bytes);
    void Free(void* marker);
    void* ObtainMarker();
    void Clear();
    void Dispose();

private:
    void* m_start;
    void* m_pos;
    size_t m_max;
    unsigned int m_size;
    bool m_disposed;
};

#endif // STACK


Stack.cpp

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "Stack.hpp"

#include <cstdlib>
#include <iostream>

Stack::Stack(unsigned int bytes)
{
    m_start = malloc(bytes);
    m_pos = m_start;
    m_max = bytes + (size_t)m_start;
    m_size = bytes;
};

Stack::~Stack()
{
    if(!m_disposed)
        Dispose();
}

void* Stack::Alloc(unsigned int bytes)
{
    std::size_t new_pos = ((size_t) m_pos) + bytes;

    if(new_pos > m_max)
        throw std::bad_alloc();

    void* output = m_pos;
    m_pos = (void*)new_pos;
    return output;
}

void Stack::Free(void* marker)
{
    m_pos = marker;
}

void* Stack::ObtainMarker()
{
    return m_pos;
}

void Stack::Clear()
{
    memset(m_start, 0, m_size);
}

void Stack::Dispose()
{
    free((void*)m_start);
    m_disposed = true;
}


Ich weiß, dass

C-/C++-Quelltext

1
#pragma once
und

C-/C++-Quelltext

1
#ifndef ... #define ... #endif
dasselbe sind, aber ich will auf Nummer sicher gehen, falls ich den Code einmal mit einem anderen Compiler testen will.

Viele, liebe Grüße,
~ EuadeLuxe ~

2

04.08.2013, 21:02

Ich würde das Ding nicht benutzen. Die Motivation scheint zu sein, das anlegen dynamischer Objekte zu beschleunigen. Weißt du, dass das wichtig sein wird, oder hast du bloß gehört, dass man das eben so machen würde?

Abgesehen davon, hat dein Code eine Reihe Probleme:

- Was macht dispose? Danach ist dein Objekt unbrauchbar und alle spätere Alloc aufrufe machen nicht mehr das, was du denkst, das sie tun (Zeiger auf gültigen Speicher liefern). Lösche die Methode und beschränke dich auf den d-tor.
- Was macht Free? Du setzt einfach nur den Speicher zurück, aber was, wenn du Objekte nicht in der Reihenfolge löschst, wie du sie anlegst? (Dann wird das nächste angelegte Objekt Speicher eines anderen Objektes benutzen - ganz schlecht)
- Wofür sollte man den Marker hohlen wollen? Das sind interne Klassendetails, die Außenstehende nichts angehen. Also biete auch keinen Zugriff darauf.
- Wie willst du Objekte anlegen? malloc und free sind kein C++ und haben unter anderem das massive Probleme, dass weder c'tor noch d'tor aufgerufen wird. Das ist mega schlecht. Mit deiner Klasse kannst du dir höchstens Speicher holen und dann sowas wie placement-new benutzen (http://www.parashift.com/c++-faq/placement-new.html). Natürlich könnte man den new Operator irgendwie überladen, die Frage ist nur, ob du weißt wie, und das auch fehlerfrei hinbekommst.

Kurz gesagt: Du tust vermutlich etwas aus falschen Motiven und bist zudem zu unerfahren, als das du es richtig tun würdest. Das wird dir einfach nur Probleme machen, und nichts bringen. Entweder du benutzt eine fertige Lösung, oder du verzichtest einfach auf Allokatoren (du kommst viel besser ohne aus, als du denkst). Fehlerhaftes Speichermanagement wird dir später extrem lustige, und schwer zu findende Fehler bescheren, sowas möchte man einfach nicht in seinem Programm haben.

Ich will gar nicht behaupten, den perfekten Stack-Allocator programmieren zu können, in Wirklichkeit habe ich damit nämlich keinerlei Erfahrung. Aber ich habe auch noch nie einen benötigt und ich sehe, dass dein Code falsch ist und so nicht benutzt werden kann. Verzichte also einfach darauf und konzentriere deine Arbeit auf sinnvollere Dinge.
Lieber dumm fragen, als dumm bleiben!

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Jonathan_Klein« (04.08.2013, 21:07)


Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

3

04.08.2013, 21:06

Also für mich sieht das so aus, als ob er nicht besonders gut funktioniert...
ich meine deine free funktion, setzt einfach den marker mitten in den stack...
Betrachte man folgendes:

C-/C++-Quelltext

1
2
3
4
5
6
Stack stacka;
void* v1 = stacka.Alloc(10);
void* v2 = stacka.Alloc(20);

// Mit diesem befehl lösche ich (vom prinzip her) v1 und v2
stacka.free(v1);


Da das ganze ein Stackallocater ist, nehme ich mal an, dass du das so willst. Ich prophezeie einfach mal, dass wenn du den in einem Projekt benutzt, du mit 99% Wahrscheinlichkeit zum Schuss ganz komische Memory-fehler wegen diesem Stack hast und die sind dann auch noch schwer zu debuggen!!!
Benutze lieber new... soo schlecht ist die performance auch nicht und wenn du schnell sachen erzeugen und zerstören willst, bleibt dir immer noch der prgramm-stack oder std::stack.
Und wenn du wirklich ganz schnell Objekte erzeugen musst und alle ganz schnell auf einmal wieder freigeben willst, empfehle ich dir einen Memory Pool. Da hast du zwar ähnliche probleme, aber man löscht nicht außversehn ein paar objekte, weil man ein anderes freigeben will (Außer natürlich du zerstörst den pool).


PS: Ich hab den memory-pool mal in einem Projekt eingesetzt, wo wirklich viele objekte auf einmal erzeugt wurden und alle gleichzeitig wieder frei gegeben wurden (wofür das ganze ja ausgelegt ist). Am Ende hat es dann aber praktisch keinen Performance Vorteil gegeben, weil das Bearbeiten und Konstruieren der Objekte im vergleich viel länger gedauert hat!

BurningWave

Alter Hase

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

4

04.08.2013, 21:06

Die Frage ist hauptsächlich, zu was brauche ich das? Warum soll nicht einfach jede Funktion/Klasse, etc. selbst Speicher reservieren können? Mit Smart-Pointern ist das sehr sauber machbar. Falls du einen Stack haben willst, bietet dir die STL entsprechende Möglichkeiten. Ich kann mir zwar vorstellen, dass du Arbeit hineingesteckt hast, die Klasse zu schreiben. Dennoch würde ich dir davon abraten, sie zu verwenden, da meiner Meinung nach nicht die Notwenigkeit einer derartigen Konstruktion besteht. Das soll nicht böse gemeint sein.

Zu deinem Code ist mir folgendes aufgefallen:
- Warum verwendest du malloc() und free() und nicht new und delete? Diese sind in C++ zu bevorzugen.
- Du verwendest m_disposed ohne es zu initialisieren. Verwende Stack::Stack(unsigned int bytes) : m_disposed(false) { [...] }
- Caste deine Zeiger doch bitte mit static_cast<>, das ist sicherer.
- Ich finde die, Vorsilbe m_ für Membervariablen ist unnötig und stört den Lesefluss.
- Du kannst darauf vertrauen, dass jeder einigermaßen moderne Compiler #pragma once kennt.

Viele Grüße

5

04.08.2013, 21:10

Danke erstmal für deine Antwort,

das Problem an der Sache is, dass ich schlecht für jeden Aufruf an Alloc() einen Pointer speichern kann. Das Ganze ist als SingleFrame-Allocator gedacht, soll heißen, dass der Allocator nach jedem Frame automatisch geleert wird. Mit der Dispose()-Methode hast du recht, die brauche ich wirklich nicht. Ich hatte sie anfangs als Test benutzt. Zum Löschen über Free(): Darüber soll lediglich der benutzte Speicher freigegeben werden. Zum Löschen des darin abgelegten Objektes ist die Aufrufende Methode verantwortlich.

Wie meinst du das, dass man auch ohne Allokatoren weit kommt? Du hast recht, ich habe bisher lediglich zig Bücher gelesen, und mit OpenGL rumprobiert, also die "Theorie" gelernt, aber noch nie das Zusammenspiel einer "Engine" gebaut. Momentan helfe ich beim Foren-Projekt, um etwas "praktische Erfahrung" zu gewinnen. Angenommen allerdings ich bräuchte unter allen Umständen einen solchen Allokator, wie würde ich dann die Free()-Methode ausbessern (Die Dispose()-Methode kommt weg)?

Liebe Grüße,
~ EuadeLuxe ~

6

04.08.2013, 21:18

Gleich noch ´ne Antwort

Entschuldigt, ich hatte eure beiden Antworten, beim Schreiben meiner Antwort noch nicht vor Augen :S.

Einen Memory-Pool wollte ich als Nächstes bauen. In der Tat mache ich das Ganze nur zur Übung. Wie oben genannt, habe ich bisher nur "theoretische" Erfahrung, und weiß daher nicht wirklich, ob das so notwendig ist. Wie würde ich den Speicher in der Free-Methode effektiv löschen? Außerdem stellt sich mir die Frage, was passiert, wenn ich überall new und delete Aufrufe hätte. Ich hatte nie so viele Aufrufe, daher weiß ich das nicht, aber würde das nicht zu Speicher-Fragmentierung führen? Wenn ja, wie könnte ich einer solchen dann vorbeugen?

Viele, liebe Grüße,
~ EuadeLuxe ~

PS.: Ich bin niemandem böse, ich wollte eure Meinungen und Verbesserungsvorschläge hören, und die höre ich mir lernbereit an ;).

BurningWave

Alter Hase

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

7

04.08.2013, 21:29

Du kannst ohne Probleme in einem Frame sehr oft new und delete aufrufen. CPU-Zeit wird vor allem beim Iterieren durch lange Listen und verarbeiten der enthaltenen Elemente verbraucht (so meine Erfahrung mit vielen Algorithmen). Es kommt auch immer darauf an, welche Objekte du mit new erzeugst. Ist der Konstruktor eines zu erzeugendes Objekts sehr komplex, ist der Aufruf von new natürlich zeitintensiver. Allerdings wäre das ein designtechnisches Problem und kein Problem der Speicherreservierung. Das Betriebssystem verwaltet den RAM und sorgt dafür, dass dieser nicht fragmentiert wird.

8

04.08.2013, 21:31

Danke für deine Antwort, BurningWave,

noch eine Frage habe ich: angenommen ich entwickle für eine Konsole, oder ein anderes Betriebssystem, das keinen virtuellen Speicher zur Verfügung stellt, wie könnte ich fragmentierten Speicher wieder defragmentieren?

Liebe Grüße,
~ EuadeLuxe ~

BurningWave

Alter Hase

Beiträge: 1 106

Wohnort: Filderstadt/Konstanz

Beruf: Student

  • Private Nachricht senden

9

04.08.2013, 21:49

Dazu müsstest du den Speicher wirklich komplett selbst verwalten und entsprechende Funktionen schreiben. Dies wird dir in der Praxis allerdings nie begegnen (außer du programmierst ein Betriebssystem für PC oder Mikroprozessor).

10

04.08.2013, 21:52

Also gut, dann versuche ich es mit einfachen new und delete Operationen. Vielen Dank für all eure Hilfe, auf euch kann man echt zählen, wenn man fragen hat ;).

Viele, liebe Grüße,
~ EuadeLuxe ~

Werbeanzeige