C++11: Alignment of sizeof()

rubricanis

Homo ludens
Wenn ich die Größe einer Klasse/Struktur mit sizeof() abfrage wird da automatisch evtl. notwendiges alignment mit berücksichtigt oder ist das "raw". Der Anlass der Frage ist ob ich die Elemente einfach in der Reihenfolge in einen unsigned char[] unterbringen kann oder ein extra alignment notwenig ist.

Peter
 
Mmmm, ich habe mal ein paar tests gemacht:
Code:
struct Test { bool x; };
sizeof(Test) --> 1
Aber:
Code:
struct Test { int x; bool y; };
sizeof(int) --> 4
sizeof(Test) --> 8
Ein wenig verwirrend. Ist wohl besser to align explicit ...
 
Das Padding ist bei sizeof() mit drin. Aber das hilft Dir nicht dabei char[] ordentlich zu alignen.

Unter x86 sollte das trotzdem gehen (alignment ist dort nicht notwendig) aber das fällt wahrscheinlich in die implementation defined Klasse.

Wieso willst Du das denn machen? Willst Du etwas serialisieren?
 
Mmmm, ich habe mal ein paar tests gemacht:
Code:
struct Test { bool x; };
sizeof(Test) --> 1
Aber:
Code:
struct Test { int x; bool y; };
sizeof(int) --> 4
sizeof(Test) --> 8
Ein wenig verwirrend. Ist wohl besser to align explicit ...
Das ganze heißt padding. Structs werden passend zum größten enthaltenen Typ gepadded, damit alle werte in einem Struct algined sind. Auch wenn man einen Array hat.
 
Wieso willst Du das denn machen? Willst Du etwas serialisieren?
Ich versuche einen ShortVector zu implementieren: template<typename T> ShortVev: public VecBase<N>{}. Um die Profiliration von Code zu reduzieren sollen die ersten N Elemente typeless in VecBase untergebracht werden. Erst wenn N/sizeof(T) > BufferSize ist geht es auf den Heap. LLVM macht etwas ähnliches.
 
Das ganze heißt padding. Structs werden passend zum größten enthaltenen Typ gepadded, damit alle werte in einem Struct algined sind. Auch wenn man einen Array hat.
OK, das heißt wenn ich eine allgemeine Lösung will die nicht davon abhängig ist wie die Structs intern ausgelegt sind macht es Sinn die Elemente auf eine sizeof(void*) oder sizeof(ptrdiff_t) Grenze zu legen, also 4 byte bei 32 bit bzw. 8 byte bei 64 bit, - oder immer gleich auf 8 byte.
 
Hmmm, aber eigentlich ist es egal. Wenn die Structs eh gepaddet werden kann ich den byte* direkt in einen T* casten. Spart Platz und Laufzeit.

Dann werd ich das mal so machen...
Vielen Dank !

Peter

PS: Wow, watt'n denglisch :)
 
OK, das heißt wenn ich eine allgemeine Lösung will die nicht davon abhängig ist wie die Structs intern ausgelegt sind macht es Sinn die Elemente auf eine sizeof(void*) oder sizeof(ptrdiff_t) Grenze zu legen, also 4 byte bei 32 bit bzw. 8 byte bei 64 bit, - oder immer gleich auf 8 byte.
Warum willst du da von Hand drin rumfuhrwerken? Wenn du ein Array anlegst, dann kümmert sich der Compiler um die relevanten Details was padding und alignment angeht. Dazu müsstest du halt auch VecBase mit dem Typen parametrisieren, aber das ist unter der Perspektive der Typsicherheit ohnehin eine gute Idee. Und um den Code-Bloat sollte sich der Compiler bzw. Linker kümmern, immerhin erhebt C++ ja den Anspruch, eine High-Level Sprache zu sein.
 
@darktrym: Ganz so einfach ist das nicht: Auch ein Array ist eine Struktur, aber keine Struct; padding heißt nicht auffüllen denn mit was wird da "gefüllt"; alles mögliche wird irgenwie ausgerichtet, aber nicht unbedingt auf eine Byte- oder Wordgrenze. Gut, mit umwandeln statt casten könnte ich leben... :rolleyes:

So werden wir denn mit denglisch leben müssen oder eben gleich englisch reden. Aber dann gingen uns die Subtilitäten der deutschen Sprache verloren was zu bedauern wäre. Stolpern wir also ruhig in denglisch weiter.... :)
 
Warum willst du da von Hand drin rumfuhrwerken? Wenn du ein Array anlegst, dann kümmert sich der Compiler um die relevanten Details was padding und alignment angeht. Dazu müsstest du halt auch VecBase mit dem Typen parametrisieren, aber das ist unter der Perspektive der Typsicherheit ohnehin eine gute Idee. Und um den Code-Bloat sollte sich der Compiler bzw. Linker kümmern, immerhin erhebt C++ ja den Anspruch, eine High-Level Sprache zu sein.
Dann ist VecBase überflüssig und ich kann ShortVec gleich entsprechend parametrisieren. Das führt aber dazu dass der ganze Code-Kladderatsch für jeden Vector Type extra kompiliert wird, auch eine Menge an Trivialitäten. Ist sicherlich einen Tuk schneller, generiert aber eine Menge mehr an Code was so vermieden wird. (s.a LLVMs Small Vector.h ). Das ist ja eine allgemeine Lösung um CodeBloat zu vermeiden.

Ich experementiere mit folgendem:
Code:
template <Int BUFFER_SIZE = 256> // in bytes
class VecBase{
public:
    Int size() const noexcept {return sz;}
    ....
protected:
     VecBase(Int elemSize): sz(0), elemSize(elemSize), vec(buffer){}

     Int   sz, elemSize;
     byte    *vec;
     union {
          Int   space;
          byte buffer[BUFFER_SIZE];
     };
};

template<typename T> class ShortVec: public VecBase<>{
  public:
     ShortVec(): VecBase(sizeof(T)){}
    ...
};
Derart verschwindet ein guter Teil des Codes in VecBase dass nur rekompiliert wird wenn eine andere Größe des Buffers verwendet werden soll was eher selten oder überhaupt nicht der Fall sein dürfte. Es geht hier ja um kleine Vectoren die hauptsächlich auf dem Stack verwendet werden sollen.

Nachtrag: Int ist ptrdiff_t denn ich allgmein als Integer-Type verwende da mir ein unsigned int als size_t allzu unlogisch erscheint. Und byte* statt void* verwende ich da der Compiler meckert wenn ich mit void* rechnen will. Aber es ist noch alles im Fluss...
 
OK, erst mal gibt es keinen Grund da irgendwo etwas zu casten. Dafür sind Templates ja da.

2. std::string tut das was Du willst für kurze Strings, ob das auch für std::vector gilt weiß ich nicht. Aber die Klasse macht auf jeden Fall Overprovisioning. Ob Du das outperformen kannst ist zumindest fragwürdig.
 
OK, erst mal gibt es keinen Grund da irgendwo etwas zu casten. Dafür sind Templates ja da.
Nöö, wenn ich das Objekt im Buffer speichere (byte*) ist im ShortVec<T> ein cast nach T erforderlich, - aber auch wenn ich einen guten Teil der weiteren Funktionalität in VecBase packe. Klar, als Client sehe ich dann nur T* oder T&.
std::string tut das was Du willst für kurze Strings, ob das auch für std::vector gilt weiß ich nicht. Aber die Klasse macht auf jeden Fall Overprovisioning. Ob Du das outperformen kannst ist zumindest fragwürdig.
Müßte man natürlich messen, aber ich glaube schon. Die Idee hatte ich nachdem ich mich mehr mit LLVM beschäftigt hatte die entsprechendes machen. Ich denke die machen das nicht aus Jux und Tollerei wenn sie keine Notwendigkeit dazu sähen und sicherlich haben die auch Messungen gemacht. Der Hintergrund ist dass es wg. der unzureichenden Effizens von std::string bei LLVM mal einen Streit gab ob man bei strings auf char* zurückgreifen müßte oder eben optimierte Klassen entwickelt. Sie haben sich für das zweite entschieden und arbeiten außerdem viel mit Slices die einen großen Teil der eigentliche Arbeit machen. Das Konzept gefällt mir gut und ich habe das konkret für Strings und String-Slices schon implementiert.

Die StdLib macht a.G. ihrer sehr allgmeinen Auslegung ja eine Menge mehr: thread safety, ref counts und was weiß ich noch alles. Sicherlich ist das alles gut optimiert aber ein gewisser Overhead bleibt da mit Sicherheit übrig. Man bekommt eben nichts umsonst. Ich denke das gilt für C++ insgesammt das eben nur sehr bedingt eine Heigh-Level Sprache ist. Die Stärke von C++ ist ja dass man leicht auf primitivere Mechanismen zurückgreifen und die in Templates und/oder Klassen einbetten kann .

Im übrigen ist das alles auch ein Lern-Projekt um mich mit den Ecken und Kanten von C++ vertraut zu machen und zu sehen wie solche Sachen gemacht werden oder gemacht werden können. Ob sich der Zeitufwand lohnt - was immer das heißen mag, und der ist nicht unbeträchtlich - interessiert mich nicht. Effizenz interessiert mich nur im Code, nicht was mein eigenes Zeit-Budget angeht. Und das Ganze ist interessant und macht Spass was mir am wichtigsten ist. :)
 
Nöö, wenn ich das Objekt im Buffer speichere (byte*) ist im ShortVec<T> ein cast nach T erforderlich, - aber auch wenn ich einen guten Teil der weiteren Funktionalität in VecBase packe. Klar, als Client sehe ich dann nur T* oder T&.
Du kannst auch in Deiner Klasse T* oder T[] verwenden. Es gibt keinen Grund dafür char* zu verwenden.
 
Nein, ein Array ist ein Feld mit gleichgroßen Einheiten, eine Struktur kapselt nur irgendwas. Und Padding heißt Auffüllen, mir ist kein technisches Wörterbuch bekannt wo es anders definiert ist. Zudem trifft das auch den Kern der Sache. Man kann hier ganz gut deutsche Begriffe verwenden, die sind alle gebräuchlich.
 
Auch Arrays haben eine Struktur: xxxxxx. Und Structs eben eine andere: xyz. Und ich glaube nicht dass beim Padding etwas tatsächlich aufgefüllt wird, z.B. mit \0. Vermutlich wird das nur gemacht um einen regulären Zugriff per Pointer auf die Daten zu haben. Insofern trifft das Wort "auffüllen" den Sachverhalt nicht wirklich. Das sind eben so die Eigenheiten von Fach-Terminologien und man verwendet besser die eingeführten Begriffe anstatt sich mit zwanghaften Übersetzungen abzuplagen. Aber ich gebe zu: Man kann es - wie ich oben - auch übertreiben und dann wird es in der Tat furchtbar! Mea culpa, mea maxima culpa! :)
 
Zurück
Oben