STL Container

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Youka
Beiträge: 28
Registriert: 20.04.2011, 18:24
Wohnort: Darmstadt

STL Container

Beitrag von Youka »

Ich speichere in einem Programm eine Vielzahl an Vektoren und Strings, muss allerdings auch stark Speicher sparen.
Nun habe ich mir mal die Größe von STL String und Vector Instanzen ausgeben lassen und bin entsetzt:
std::string -> 28 Byte
std::vector -> 24 Byte

Allein beim String kann ich mir den Content-Zeiger (32bit Maschine = 4 Byte), die Länge (4 Byte), Speichergröße (4 Byte) und den zweiten Zeiger (mit terminierender Null; 4 Byte) erklären, aber woher der restliche Speicherverbrauch? Beim Vektor sollten es sogar nur 8 Byte gesamt sein...
Nun wollte ich meine eigenen Container schreiben:

Code: Alles auswählen

template<class T>
class tinyvector{
	private:
		// Data
		T *m_data;
		size_t m_size;
		size_t m_capacity;
	public:
		// Methods
		T& at(size_t i){
			return this->m_data[i];
		}
		size_t size(){
			return this->m_size;
		}
		size_t capacity(){
			return this->m_capacity;
		}
		void reserve(size_t n){
			if(this->m_data){
				T *new_data = new T[n];
				memcpy(new_data, this->m_data, sizeof(T) * (this->m_capacity < n ? this->m_capacity : n));
				delete this->m_data;
				this->m_data = new_data;
			}else
				this->m_data = new T[n];
			if(n < this->m_size)
				this->m_size = n;
			this->m_capacity = n;
		}
		void clear(){
			if(this->m_data){
				delete this->m_data;
				this->m_data = 0;
				this->m_size = 0;
				this->m_capacity = 0;
			}
		}
		void assign(const tinyvector<T>& x){
			this->clear();
			this->m_data = new T[x.m_capacity];
			memcpy(this->m_data, x.m_data, sizeof(T) * x.m_size);
			this->m_size = x.m_size;
			this->m_capacity = x.m_capacity;
		}
		void push_back(const T& x){
			if(this->m_capacity == 0)
				this->reserve(1);
			else if(this->m_size == this->m_capacity)
				this->reserve( this->m_capacity << 1);
			this->m_data[this->m_size++] = x;
		}
		// Operators
		T& operator[](size_t i){return this->at(i);}
		tinyvector<T>& operator=(const tinyvector<T>& x){this->assign(x); return *this;}
		// Ctors & dtor
		tinyvector() : m_data(0), m_size(0), m_capacity(0){}
		explicit tinyvector(size_t n) : m_data(new T[n]), m_size(0), m_capacity(n){}
		explicit tinyvector(T *data, size_t n) : m_data(data), m_size(n), m_capacity(n){}
		tinyvector(const tinyvector<T>& x){this->assign(x);}
		~tinyvector(){this->clear();}
};
Der Compiler (MSVC++ 2008) akzeptiert den Code, jedoch stürzt das Programm sofort zum Start ab.
Fehlermeldung:
Problemereignisname: APPCRASH
Anwendungsname: mpc-hc.exe
Anwendungsversion: 1.5.3.3819
Anwendungszeitstempel: 4ebc2a94
Fehlermodulname: StackHash_5477
Fehlermodulversion: 6.0.6001.18538
Fehlermodulzeitstempel: 4cb733dc
Ausnahmecode: c0000374
Ausnahmeoffset: 000b0dbc
Betriebsystemversion: 6.0.6001.2.1.0.768.3
Gebietsschema-ID: 1031
Zusatzinformation 1: 5477
Zusatzinformation 2: 89ab21718fa0504e997ae585ca389584
Zusatzinformation 3: 6be0
Zusatzinformation 4: 6be4bf2306cae4afb92ab26f284e7e49
Woran kann dies liegen? Worin unterscheidet sich der STL Vektor von meinem, dass er funktioniert?
Benutzeravatar
Krishty
Establishment
Beiträge: 8245
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: STL Container

Beitrag von Krishty »

Youka hat geschrieben:std::string -> 28 Byte
std::vector -> 24 Byte

Allein beim String kann ich mir den Content-Zeiger (32bit Maschine = 4 Byte), die Länge (4 Byte), Speichergröße (4 Byte) und den zweiten Zeiger (mit terminierender Null; 4 Byte) erklären, aber woher der restliche Speicherverbrauch? Beim Vektor sollten es sogar nur 8 Byte gesamt sein...
Debug oder Release? Die Größe von vector sollte in Visual C++ 2010 Release 16 B sein; in Visual C++ 2012 Release 12. Dort kommt nämlich zusätzlich zu Anfang und Ende capacity hinzu; die weiteren 4 B vor Visual Studio 2012 kommt daher, dass nicht die Empty Base Class Optimization ausgenutzt wurde um zustandslose Allokatoren in den Objekten zu speichern.

Warum glaubst du, dass du mit kleineren string- und vector-Klassen Speicher sparen wirst? Diese Klassen sind zur Datenverwaltung da; den größten Teil verbrauchen üblicherweise die Daten selber, also was du darin speicherst.

Für welches System entwickelst du? Heute wird kein PC aufgrund von vector<string> an seine Kapazitätsgrenze stoßen (ich habe Versuche erlebt), darum tippe ich auf Embedded?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: STL Container

Beitrag von Helmut »

Dein vector ruft den Konstruktor von Objekten auf, die im Vektor nur reserviert sind. Außerdem werden sie ohne Move Konstruktor verschoben und Exception-sicher ist das ganze natürlich auch nicht. Solange du aber nur PODs damit benutzt sollte es eigentlich gehen, Zeig mal ein Beispiel, das die Klasse benutzt.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: STL Container

Beitrag von CodingCat »

Und tinyvector(const tinyvector<T>& x){this->assign(x);} initialisiert überhaupt nichts, mindestens einen Crash wert.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: STL Container

Beitrag von dot »

Ich würde mir an deiner Stelle besser mal überlegen, wieso ich eigentlich so viele vector und string Objekte hab, dass deren Overhead zum Problem wird...
Benutzeravatar
Krishty
Establishment
Beiträge: 8245
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: STL Container

Beitrag von Krishty »

Ganz genau. Ich kann Versuche, STL-Container durch Eigenwerke zu ersetzen, nachzuvollziehen, wenn sie durch Vereinfachung der Programmstruktur, Einschränkung des Zustandsraums, oder Ähnliches begründet sind – aber hier bin ich ganz schön hellhörig, wo denn dieser exotische Flaschenhals Container-Speicher-Unkosten herkommt.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Youka
Beiträge: 28
Registriert: 20.04.2011, 18:24
Wohnort: Darmstadt

Re: STL Container

Beitrag von Youka »

CodingCat hat geschrieben:Und tinyvector(const tinyvector<T>& x){this->assign(x);} initialisiert überhaupt nichts, mindestens einen Crash wert.
Arg, du hast absolut Recht, zumindest das m_data muss auf 0 gesetzt werden.
dot hat geschrieben:Ich würde mir an deiner Stelle besser mal überlegen, wieso ich eigentlich so viele vector und string Objekte hab, dass deren Overhead zum Problem wird...
Ich lasse Daten aus einer Text-Datei auslesen und im RAM ablegen, wobei auch Exceptions geworfen werden können und daher z.B. Strings in einem Vektor keine Zeiger auf dynamischen Speicher sein sollten. Ich mache es mir vielleicht auf diese Art etwas zu gemütlich...
Krishty hat geschrieben:Debug oder Release?
Release.
Krishty hat geschrieben: Warum glaubst du, dass du mit kleineren string- und vector-Klassen Speicher sparen wirst? Diese Klassen sind zur Datenverwaltung da; den größten Teil verbrauchen üblicherweise die Daten selber, also was du darin speicherst.
Ich will halt "jeden Byte zweimal umdrehen", nenn mich geizig ;)
Krishty hat geschrieben: Für welches System entwickelst du? Heute wird kein PC aufgrund von vector<string> an seine Kapazitätsgrenze stoßen (ich habe Versuche erlebt), darum tippe ich auf Embedded?
Es werden benutzererststellte Dateien ausgelesen, welche ziemlich groß werden können, und dessen Daten gespeichert, deswegen will ich die Grenze möglichst weit hoch stecken.
Helmut hat geschrieben:Dein vector ruft den Konstruktor von Objekten auf, die im Vektor nur reserviert sind. Außerdem werden sie ohne Move Konstruktor verschoben und Exception-sicher ist das ganze natürlich auch nicht. Solange du aber nur PODs damit benutzt sollte es eigentlich gehen.
Der vector hält nur Strukturen mit default-constructor, eigentlich sicher.

-------------------------

Danke erstmal für die rege Beteiligung und hilfreichen Antworten.
Mich interessiert immer noch der Ursprung des "StackHash"-Fehlers, weil der Copy-Constructor eigentlich einen c0...05 Fehler hätte geben müssen.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: STL Container

Beitrag von dot »

Youka hat geschrieben:
dot hat geschrieben:Ich würde mir an deiner Stelle besser mal überlegen, wieso ich eigentlich so viele vector und string Objekte hab, dass deren Overhead zum Problem wird...
Ich lasse Daten aus einer Text-Datei auslesen und im RAM ablegen, wobei auch Exceptions geworfen werden können und daher z.B. Strings in einem Vektor keine Zeiger auf dynamischen Speicher sein sollten. Ich mache es mir vielleicht auf diese Art etwas zu gemütlich...
Und wie genau sehen diese Daten aus? Und wie legst du sie ab? Wieso genau brauchst du dabei nun so viele vector und string Objekte!?
Benutzeravatar
Schrompf
Moderator
Beiträge: 4856
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: STL Container

Beitrag von Schrompf »

Die Vektoren / Strings unter VC10 sind unter anderem auch so groß, weil sie kleine Datenmengen (AFAIK bis 16 Byte) ohne Allokation aufnehmen können. Dieses kleine Feature hat mir so unglaubliche Performance-Verbesserungen gebracht, dass ich VC2012s Werbung mit "std::vector nur noch 12 Byte groß" mit großem Argwohn gelesen habe. Ich empfinde diese Optimierung als ziemlich wichtig. Zumal Du ja auch bedenken musst, dass jede Allokation für die reine Verwaltung des Heaps immer ein paar Byte größer wird als was Du allokiert hast. Auch Deine Implementation, die ab dem ersten Byte Inhalt bereits allokiert, wird also nicht zwangsweise speichersparender sein.

Ich persönlich würde Dir raten, wenn Du die ganzen Daten eh aus einer Datei rausparst, dann doch einfach String-Referenzen zu speichern, bei der ein String einfach auf Anfang und Ende in der geparsten Datei verweist. Komplett allokationsfrei und auf 32Bit nur 8 Byte groß.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: STL Container

Beitrag von CodingCat »

Youka hat geschrieben:Ich speichere in einem Programm eine Vielzahl an Vektoren und Strings, muss allerdings auch stark Speicher sparen.
Nun habe ich mir mal die Größe von STL String und Vector Instanzen ausgeben lassen und bin entsetzt:
std::string -> 28 Byte
std::vector -> 24 Byte

Allein beim String kann ich mir den Content-Zeiger (32bit Maschine = 4 Byte), die Länge (4 Byte), Speichergröße (4 Byte) und den zweiten Zeiger (mit terminierender Null; 4 Byte) erklären, aber woher der restliche Speicherverbrauch? Beim Vektor sollten es sogar nur 8 Byte gesamt sein...
Nun wollte ich meine eigenen Container schreiben:
Dein Vektor hat in der aktuellen MS-STL-Implementierung exakt dieselbe Größe wie ein STL-Vektor. Wenn nicht, liegt das an Debug-Features, diese sind via _HAS_ITERATOR_DEBUGGING=0 und _SECURE_SCL=0 ohne Weiteres abstellbar. "Tiny" ist dein Vektor jedenfalls ganz sicher nicht. Siehe auch Schrompfs Hinweise zu Allokations-Overhead.
Youka hat geschrieben:
CodingCat hat geschrieben:Und tinyvector(const tinyvector<T>& x){this->assign(x);} initialisiert überhaupt nichts, mindestens einen Crash wert.
Arg, du hast absolut Recht, zumindest das m_data muss auf 0 gesetzt werden.
dot hat geschrieben:Ich würde mir an deiner Stelle besser mal überlegen, wieso ich eigentlich so viele vector und string Objekte hab, dass deren Overhead zum Problem wird...
Ich lasse Daten aus einer Text-Datei auslesen und im RAM ablegen, wobei auch Exceptions geworfen werden können und daher z.B. Strings in einem Vektor keine Zeiger auf dynamischen Speicher sein sollten. Ich mache es mir vielleicht auf diese Art etwas zu gemütlich...
Die Idee hinter Exceptions ist es ja, Fehlerbehandlung sinnvoll, vollständig und "gemütlich" gestaltbar zu machen, daran ist absolut nichts falsch.

Falsch ist, dass die Aufteilung großer Datenmengen in viele kleine Container die Sache gemütlicher macht. Wenn du einen großen vector hast, in dem alle Daten enthalten sind, ist der Overhead des vectors mickrig. Mit Offsets/Zeigern kannst du diese Datenmenge kompakt und wesentlich direkter strukturieren, der vector garantiert dir Exception-Sicherheit.

Davon abgesehen ist dein Code ziemlich schludrig, schon dein push_back ist nicht Exception-sicher. Richtig wäre, erst zuzuweisen und dann den Zähler zu erhöhen, andersrum (so wie es jetzt ist) hast du nach jeder Exception ein ungültiges Element darin. Auch die Dopplungen wird der Compiler nicht alle rausoptimieren. Die Verzweigung zu reserve(1) und reserve(this->m_capacity << 1) kriegt er vielleicht noch hin, den new[]-Aufruf hast du aber ziemlich sicher auch mehrfach im optimierten Maschinencode.
Schrompf hat geschrieben:Die Vektoren / Strings unter VC10 sind unter anderem auch so groß, weil sie kleine Datenmengen (AFAIK bis 16 Byte) ohne Allokation aufnehmen können. Dieses kleine Feature hat mir so unglaubliche Performance-Verbesserungen gebracht, dass ich VC2012s Werbung mit "std::vector nur noch 12 Byte groß" mit großem Argwohn gelesen habe.
Sicher? Vektoren hatten meines Wissens nach nie Small-String-Optimization, und das ist auch gut so. Die Werbung für 12-Byte-Vektoren bezog sich wie Krishty bereits sagte auf eine versäumte Optimierung bei leeren Allokatoren. An der Small-String-Optimization in Strings sollte sich auch in VS2012 nichts geändert haben.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: STL Container

Beitrag von eXile »

CodingCat hat geschrieben:Wenn nicht, liegt das an Debug-Features, diese sind via _HAS_ITERATOR_DEBUGGING=0 und _SECURE_SCL=0 ohne Weiteres abstellbar. "Tiny" ist dein Vektor jedenfalls ganz sicher nicht. Siehe auch Schrompfs Hinweise zu Allokations-Overhead.
Nimm _ITERATOR_DEBUG_LEVEL = 0. Seit Visual C++ 2010 sind _SECURE_SCL und _HAS_ITERATOR_DEBUGGING deprecated. ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8245
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: STL Container

Beitrag von Krishty »

Schrompf hat geschrieben:Ich persönlich würde Dir raten, wenn Du die ganzen Daten eh aus einer Datei rausparst, dann doch einfach String-Referenzen zu speichern, bei der ein String einfach auf Anfang und Ende in der geparsten Datei verweist. Komplett allokationsfrei und auf 32Bit nur 8 Byte groß.
Exakt. Wenn du die Datei in den Speicher mappst, gehört sie außerdem nicht zum privaten Arbeitssatz deines Prozesses, sondern wird mit anderen Prozessen und Windows’ Datei-Cache geteilt werden – also null Bytes Speicher verbrauchen und in null Sekunden laden. Das einzige, was dein Programm dann noch allokiert, sind die acht Bytes pro String deiner Verweisliste. Da dann alle Strings als geteilter / fremder Speicher angesehen werden, muss auch nichts verwaltet oder wieder freigegeben werden.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Helmut
Establishment
Beiträge: 237
Registriert: 11.07.2002, 15:49
Wohnort: Bonn
Kontaktdaten:

Re: STL Container

Beitrag von Helmut »

Youka hat geschrieben:
Helmut hat geschrieben:Dein vector ruft den Konstruktor von Objekten auf, die im Vektor nur reserviert sind. Außerdem werden sie ohne Move Konstruktor verschoben und Exception-sicher ist das ganze natürlich auch nicht. Solange du aber nur PODs damit benutzt sollte es eigentlich gehen.
Der vector hält nur Strukturen mit default-constructor, eigentlich sicher.
Naja du darfst auch keine Objekte mit Destruktor oder Zuweisungsoperator darin speichern. Z.B. std::string.
Benutzeravatar
dot
Establishment
Beiträge: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: STL Container

Beitrag von dot »

Helmut hat geschrieben:
Youka hat geschrieben:
Helmut hat geschrieben:Dein vector ruft den Konstruktor von Objekten auf, die im Vektor nur reserviert sind. Außerdem werden sie ohne Move Konstruktor verschoben und Exception-sicher ist das ganze natürlich auch nicht. Solange du aber nur PODs damit benutzt sollte es eigentlich gehen.
Der vector hält nur Strukturen mit default-constructor, eigentlich sicher.
Naja du darfst auch keine Objekte mit Destruktor oder Zuweisungsoperator darin speichern. Z.B. std::string.
Huch? Seit wann denn das? Und wieso?
Benutzeravatar
CodingCat
Establishment
Beiträge: 1857
Registriert: 02.03.2009, 21:25
Wohnort: Student @ KIT
Kontaktdaten:

Re: STL Container

Beitrag von CodingCat »

Weil er in seinem tiny_vector mit memcpy verschiebt und gleichzeitig mit delete freigibt. übrigens steckt da gleich der nächste Crash, das muss auf jeden Fall delete[] this->m_data heißen, nicht delete this->m_data.
alphanew.net (last updated 2011-07-02) | auf Twitter | Source Code: breeze 2 | lean C++ library | D3D Effects Lite
Antworten