Speicheroptimierung: was ist sinnvoll, was nicht?



  • Hallo,

    ich mache mir gerade Gedanken zur Speicheroptimierung und darüber, was wann wie sinnvoll ist. Klar, auf dem PC fällt es nicht so sher ins Gewicht ob eine Klasse 8 byte oder 6 byte benötigt, jedoch kann es auf der ein oder anderen exotischen Hardware wie Smartphones, Playstation und Konsorten ... durchaus von Belang sein.

    Um mal dabei zu bleiben, nehmen wir an ich habe eine Klasse VertexBuffer

    class VertexBuffer
    {
       ? handle;
       ? Info1;
       ? Info2; 
    };
    

    Wobei handle ein integer und info jeweils ein enum mit zuständen darstellt, insgesammt also (4 + 4 + 4 🙂 12 byte. Man könnte hier die größe um die hälfte reduzieren, etwa indem man statt dem int und den enum werten ein short type nimmt. Klar, das handle wäre dann halb so groß und könnte nur noch rund 66k werte aufnehmen, ist aber aktuell nicht von belang, immerhin hätten wir die größe der Klasse um die hälfte reduziert.

    Die enum-werte müssten dann zwar mittels typecast verwendet werden, aber das dürfte ja kompilerzeitabhängig sein und sich nicht auf die runtime auswirken.

    Meine Frage auch insbesondere an Leute mit Erfahrung auf solch exotischen Systemen, was macht eurer Meinung nach Sinn, und was geht selbst für euch zu weit?

    Vielen Dank für eure Antworten.



  • premature optimization is the root of all evil.



  • Das kann durchaus Sinn machen, und für sowas nimmt man die typedefs aus <cstdint>. Du kannst auch den underlying_type eines enum direkt festlegen, etwa so:

    enum class blahblubb : std::uint16_t
    {
    };
    

    Aber, ein VertexBuffer (wenn wir hier mal von OpenGL reden) ist so ein "dickes" Handle*, dass die Wahrscheinlichkeit ziemlich gering ist dass deine "Optimierung" hier irgendetwas bewirken wird, egal auf welchem System.

    * Heißt, davon willst du eh nicht 10-Tausende in deinem Programm haben.



  • Wie gross ein Handle ist kann man sich normalerweise nicht aussuchen, das bestimmt das OS/die API des Geräts mit dem man arbeitet. Die programmiert keiner selbst. Ausser natürlich den Leuten die genau das, und nur das machen, also OS/Treiber/Firmware für diverse Geräte entwickeln.

    Pria schrieb:

    Meine Frage auch insbesondere an Leute mit Erfahrung auf solch exotischen Systemen, was macht eurer Meinung nach Sinn, und was geht selbst für euch zu weit?

    Der Umfang dieser Frage ist mMn. viel zu gross für "mal eben schnell im Forum gefragt". Darüber könnte man lange Papers oder sogar ganz Bücker schreiben.



  • Edit: Hier stand Mist.



  • Tobiking2 schrieb:

    Es bringt in vielen Fällen gar nichts, wenn man Datentypen verwendet, die kleiner als die Wortgröße sind. Viele Compiler sorgen bei Structs und Klassen dafür das jeder Wert im Speicher genau eine Wortgröße Platz hat (Stichwort Padding und Alignment).

    Machen die wenigsten. Also genaugenommen kenne ich keinen einzigen.
    Kleine Typen werden klein aligned, grosse Typen gross. Nen 8 bittigen char (*) oder 16 bittigen short auf 4 Byte zu alignen macht keiner, weil es auch keinen Sinn macht.

    Damit wird gesorgt das beim holen von Daten aus dem Speicher genau ein Wert in einem Register liegt.

    Die meisten CPUs können Werte beim Laden aus dem Speicher automatisch auf Registerbreite aufblasen - sowohl unsigned als auch signed.

    Ansonsten müsste man die Werte erst noch nachträglich auf Register verteilen um ordentlich mit ihnen weiter arbeiten zu können. Das ist zusätzlicher Aufwand zur Laufzeit.

    Siehe oben, also nein. Klar, es gibt CPUs wo man das so machen muss, aber die sind heute kaum noch ein Thema.

    Deine Klasse würde also in vielen Fällen gar nicht kleiner, wenn du diese Optimierung nicht ausschalten würdest.

    Wieder siehe oben. Also: doch, würde kleiner. Ganz von selbst.

    ----

    Ich glaube du solltest selbst noch ein wenig zum Thema Structure Packing lesen, denn das meiste von dem was du schreibst stimmt nicht.

    *: Bei char ist es nichtmal erlaubt. Das einzige was erlaubt ist, ist dass man char von Haus aus grösser macht. Aber der Compiler darf zwischen zwei char s in z.B. einem Array kein Padding einfügen. In einer struct/class dürfte er es vermutlich - macht aber auch keiner.


  • Mod

    Bei char ist es nichtmal erlaubt.

    Das ist auch bei einer Variable mit zwei Bytes oder drei nicht erlaubt, da das Alignment nie größer als der Typ selbst sein darf.

    Das einzige was erlaubt ist, ist dass man char von Haus aus grösser macht.

    Was meinst du damit? char ist immer ein Byte groß.

    In einer struct/class dürfte er es vermutlich - macht aber auch keiner.

    Ja, er darf es

    Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).

    Das heißt, dass diese Faktoren es bedingen könnten, andererseits ist nirgends definiert, dass sie sonst direkt hintereinander liegen.

    Macht aber wie du sagtest sowieso keiner.



  • Arcoth schrieb:

    Bei char ist es nichtmal erlaubt.

    Das ist auch bei einer Variable mit zwei Bytes oder drei nicht erlaubt, da das Alignment nie größer als der Typ selbst sein darf.

    Bitte richtig formulieren, so ist das nämlich falsch.


  • Mod

    schluderer schrieb:

    Bitte richtig formulieren, so ist das nämlich falsch.

    Was ist denn da schluderig formuliert?



  • Arcoth schrieb:

    schluderer schrieb:

    Bitte richtig formulieren, so ist das nämlich falsch.

    Was ist denn da schluderig formuliert?

    Gut, ich präzisiere: Es ist falsch.


  • Mod

    schluderer schrieb:

    Gut, ich präzisiere: Es ist falsch.

    Das Alignment kann nie größer als der Typ selbst sein, weil sonst Array-Elemente nicht direkt hintereinander liegen könnten, was aber immer der Fall ist. Ich sehe den Fehler nicht.



  • Arcoth schrieb:

    Bei char ist es nichtmal erlaubt.

    Das ist auch bei einer Variable mit zwei Bytes oder drei nicht erlaubt, da das Alignment nie größer als der Typ selbst sein darf.

    Tip: schluderer meint vermutlich Alignment-Requirement (ich hoffe das ist jetzt der richtige Fachbegriff) <-> Alignment. Das Alignment darf natürlich immer grösser sein. (Und ein Typ hat auch kein Alignment, ein Objekt bzw. eine Adresse hat Alignment).

    Was ich meinte: C++ garantiert dir für char dass es keine Bits gibt die man mit char nicht "sehen" kann. Für UDTs gilt das nicht. Ob es für die numerischen Typen gilt weiss ich nicht.

    Also nochmal anders formuliert: ne struct kann 8 Byte gross sein, wovon aber nur die ersten 6 verwendet werden. sizeof(T) ist dann natürlich 8, sogesehen hast du schon Recht. Trotzdem existieren hier zwei Padding-Bytes.

    Das einzige was erlaubt ist, ist dass man char von Haus aus grösser macht.

    Was meinst du damit? char ist immer ein Byte groß.

    Stell dich nicht so an. Ich hab' oben extra "8-bittig" geschrieben. Der Zusatz gilt natürlich hier auch.

    Dass der C++ Standard das Wort "byte" für "das was so gross ist wie ein char, auch wenn ein char viel mehr als 8 Bit hat" verwendet weiss ich auch. Und dass daher ein char immer ein "C++ byte" gross ist. Ob diese Verwendung des Wortes "byte" sinnvoll ist ist aber ne andere Frage.
    Man hätte es genau so gut "word" oder "smallest adressable memory unit" nennen können. Dann wären Generationen von C++ Programmierern weniger verwirrt, und es gäbe eine Sache weniger mit der Generationen von Schlaumeiern rumnerven könnten.


  • Mod

    Arcoth schrieb:

    schluderer schrieb:

    Gut, ich präzisiere: Es ist falsch.

    Das Alignment kann nie größer als der Typ selbst sein, weil sonst Array-Elemente nicht direkt hintereinander liegen könnten, was aber immer der Fall ist. Ich sehe den Fehler nicht.

    Alignment ist eine Eigenschaft von Objekten, nicht Typen. Der Typ erzwingt lediglich ein Mindestalignment für alle Objekte dieses Typs. Und dieses Mindestalignment ist immer ein Teiler der Größe des Typs (hier greift das Array-Argument).


  • Mod

    Tip: schluderer meint vermutlich Alignment-Requirement (ich hoffe das ist jetzt der richtige Fachbegriff) <-> Alignment. Das Alignment darf natürlich immer grösser sein.

    💡
    Ich meinte mehr oder weniger alignof(foo) <= sizeof(foo) , und ja, das ist dann wohl ein 'Alignment-Requirement' (oder wie auch immer es genannt wird).

    Ja, das Missverständnis lag wohl darin, dass ich dachte, Alignment ist eine dem Typ innewohnende Sache.

    Man hätte es genau so gut "word" oder "smallest adressable memory unit" nennen können. Dann wären Generationen von C++ Programmierern weniger verwirrt, und es gäbe eine Sache weniger mit der Generationen von Schlaumeiern rumnerven könnten.

    Bezeichnet nicht alle Welt mit Byte die kleinste adressierbare Einheit? So habe ich den Begriff aufgefasst, und demnach ist ein char auch immer ein "Byte" groß. Ich verstand nur nicht, was du "größer machen willst". Die Bitzahl?



  • Also für mich ist ein Byte eigentlich immer ein Oktett, nur hier im Forum und um C++-Standard muss man sich da anstrengen, ansonsten gibt's schnell Haue...



  • hustbaer schrieb:

    Ich glaube du solltest selbst noch ein wenig zum Thema Structure Packing lesen, denn das meiste von dem was du schreibst stimmt nicht.

    Du hast Recht. Die Werte werden natürlich anhand ihrer Größe aligned und nicht allgemein auf Wortgröße. Das würde keinen Sinn ergeben.



  • Pria == blurry ?



  • Arcoth schrieb:

    Man hätte es genau so gut "word" oder "smallest adressable memory unit" nennen können. Dann wären Generationen von C++ Programmierern weniger verwirrt, und es gäbe eine Sache weniger mit der Generationen von Schlaumeiern rumnerven könnten.

    Bezeichnet nicht alle Welt mit Byte die kleinste adressierbare Einheit? So habe ich den Begriff aufgefasst, und demnach ist ein char auch immer ein "Byte" groß. Ich verstand nur nicht, was du "größer machen willst". Die Bitzahl?

    Boah, stell dich bitte nicht dümmer als du bist.

    Ja, klar die Bitzahl. Bzw. anders gesagt wie viele "Byte" (nicht im C++ Sinn, sondern so wie der Rest der Welt den Begriff versteht) ein char gross ist.

    Ein Byte hat 8 Bit. Das ist ein De Facto Standard.
    Und Systeme wo die kleinste adressierbare Einheit grösser als 8 Bit ist nennen diese normalerweise Word und nicht Byte.
    Nur der C++ Standard muss verwirrenderweise trotzdem den Begriff Byte verwenden.



  • hustbaer schrieb:

    Wie gross ein Handle ist kann man sich normalerweise nicht aussuchen, das bestimmt das OS/die API des Geräts mit dem man arbeitet.

    Sicher, das trift aber z.b. nicht auf OGL zu (deswegen das Beispiel), da ist das Handle ein uint32 Wert mit vortlaufender Nummer, von daher kann man da z.b. mit einem ushort optimieren.

    Für das enum habe ich mal spasseshalber ein Template-Container angelegt der den Wert als 16 bit wert ablegt und konnte hier keine Unterschiede zu Performance feststellen.

    template<typename T>
    	struct cenum
    	{
    		unsigned short value;
    
    		public:
                            cenum() : value(T()) {}
    
    			T get() {return (T)value;}
    			operator T () { return (T)value; }
    			T* operator-> () {return (T*)&value;}
    
    			void operator =(T val) {value = (unsigned short)val;}
    			void set(T val) {value = (unsigned short)val;}
    	};
    

    Ich weiß jetzt natürlich nicht wie das ist bei Playstation und Co. da die ja normalerweise nicht mit OGL arbeiten



  • Pria schrieb:

    hustbaer schrieb:

    Wie gross ein Handle ist kann man sich normalerweise nicht aussuchen, das bestimmt das OS/die API des Geräts mit dem man arbeitet.

    Sicher, das trift aber z.b. nicht auf OGL zu (deswegen das Beispiel), da ist das Handle ein uint32 Wert mit vortlaufender Nummer, von daher kann man da z.b. mit einem ushort optimieren.

    Für das enum habe ich mal spasseshalber ein Template-Container angelegt der den Wert als 16 bit wert ablegt und konnte hier keine Unterschiede zu Performance feststellen.

    template<typename T>
    	struct cenum
    	{
    		unsigned short value;
    
    		public:
                            cenum() : value(T()) {}
    
    			T get() {return (T)value;}
    			operator T () { return (T)value; }
    			T* operator-> () {return (T*)&value;}
    			
    			void operator =(T val) {value = (unsigned short)val;}
    			void set(T val) {value = (unsigned short)val;}
    	};
    

    Ich weiß jetzt natürlich nicht wie das ist bei Playstation und Co. da die ja normalerweise nicht mit OGL arbeiten

    *lach*


  • Mod

    Und Systeme wo die kleinste adressierbare Einheit grösser als 8 Bit ist nennen diese normalerweise Word und nicht Byte.
    Nur der C++ Standard muss verwirrenderweise trotzdem den Begriff Byte verwenden.

    Alles klar, merke ich mir.

    Für das enum habe ich mal spasseshalber ein Template-Container angelegt der den Wert als 16 bit wert ablegt und konnte hier keine Unterschiede zu Performance feststellen.

    Natürlich nicht, weil alles geinlined wird (was ist das überhaupt für ein "Performance-Test"?). Du kannst auch gleich den unterliegenden Typ ändern, wie cooky schon viel weiter vorne demonstriert hat, dann wirst du auch rein theoretisch keine Unterschiede feststellen können.

    Übrigens, es gibt noch die ganz feine Option -fshort-enums vom GCC.


Anmelden zum Antworten