placement new/new[], Alignemnt und Arrays



  • Hallo,
    wer kennt sich damit aus?

    Ich wollte mal etwas mit placement new experimentieren. Dabei bin ich dann erstmalig auf den Begriff Alignment gestoßen.
    Ich dachte mir: Erstmal machen, und schauen obs kracht. Hat es nicht, also war ich zufrieden. Aber dann bin ich darauf gestoßen, dass statt eines Crashes auch die Performance leiden kann - das ist ja fast noch schlimmer.

    Alles sehr konfus, was ich so gelesen habe. Wenn ich das richtig sehe, war alignment vor c++11 auch kein Thema.. und der new[] ist quasi unbrauchbar. Kann mir jemand erklären warum?
    Was ist mit diesem Bsp.? Ist das soweit ok, oder sind dabei Performance-Einbußen möglich?

    T* t = (T*) new char[sizeof(T)*N];
    new(t[n]) T; // immer return &t[n] ??
    

    Mal heißt es, der Programmierer müsse sich ums Alignment kümmern, mal macht es der Copiler (zB. "sizeof() == Größe des Typs + evtl Padding"). Und was nun wann genau ( new()/new[]() ) beachtet werden muss, ist mir nicht ganz klar. Kann jemand mir das erklären?!

    Würde mir nicht besonders gefallen, wenn das wirklich Aufgabe des Programmierers sein sollte. C++ abstrahiert sonst doch den Prozessor möglichst weit weg..
    Zumal ich auch nicht wüsste, welche Maßnahmen ich überhaupt ergreifen könnte (und wann ich müsste).

    MfG
    0xMöbius


  • Mod

    Aber dann bin ich darauf gestoßen, dass statt eines Crashes auch die Performance leiden kann - das ist ja fast noch schlimmer.

    Nein, das ist überhaupt viel viel besser. Weil korrekt.

    Wenn ich das richtig sehe, war alignment vor c++11 auch kein Thema..

    Doch. Seit C++11 gibt es jedoch Sprachfeatures mit denen Alignment festgelegt und erfragt werden kann.

    Ist das soweit ok, oder sind dabei Performance-Einbußen möglich?

    Nein, dein Beispiel kann undefiniertes Verhalten haben, und zwar dann, wenn der von new[] zurückgegebene Zeiger auf ungenügend ausgerichteten Speicher zeigt. Nimm stattdessen

    T* t = (T*) new std::aligned_storage_t<sizeof(T)*N, alignof(T)>;
    new (t+n) T; // Sollte wohl nicht t[n] sein
    

    Ich wollte mal etwas mit placement new experimentieren.

    Dann brauchst du dich nicht zu beschweren, wenn derartige Nuancen dich zu kümmern haben. Es gibt Templates, die diese Technik für entsprechende Anwendungsfälle abstrahieren, e.g. boost::container::static_vector .



  • Nein, das ist überhaupt viel viel besser. Weil korrekt.

    Pah. Entweder ganz oder garnicht. 😃 Ich bin i.A. ein Fan davon wenn Programme direkt sterben, und nicht noch blutend einige Ecken weiter kommen um dann dort zu verrecken. Das erschwert nur die Arbeit des Kommissars..

    Doch. Seit C++11 gibt es jedoch Sprachfeatures mit denen Alignment festgelegt und erfragt werden kann.

    Jetzt mal von Compiler-spezifischen Erweiterungen abgesehen. Ohne Sprachfeatures hab ich doch auch keine Möglichkeit sie zu beachten / darauf einzugehen, würde ich jetzt annehmen.

    Nein, dein Beispiel kann undefiniertes Verhalten haben, und zwar dann, wenn der von new[] zurückgegebene Zeiger auf ungenügend ausgerichteten Speicher zeigt.

    Beziehst du dich jetzt nur auf "new()[]" oder auch auf "new()-ohne[]" ?? Ich habe, wie gesagt, eh irgendwo gelesen, dass new()[] unbrauchbar ist.
    Oder meinst du das normale new[] von "new char[..]". Char müsste doch immer passen, oder?!
    Davon mal abgesehen, was meinst du mit "ungenügend ausgerichtetem Speicher" - oder was wäre ein englisches Schagwort?
    Ich war der Meinung, bei der Stelle "T* t = (T*) new char[sizeof(T)*N];" hätte ich mir am sichsersten sein können.
    Wenn das Alignment im sizeof() berücksichtigt wird, warum brauche ich dann noch den alignof()? Ich denke beim Alignment immer an structs, die bei bedarf (je nach CPU) mit anonymen char[] aufgefüllt werden. Mehr aber auch nicht. Also fehlt da was?
    ..Ja, meinte natürlich t+n oder &t[n]..

    Dann brauchst du dich nicht zu beschweren, wenn derartige Nuancen dich zu kümmern haben.

    Ich beschwere mich doch nicht. Offenbar will ich wohl ins Std-Komitee, weil ja sonst niemand new / delete nutzen darf 😉
    Placement new gab es doch schon vor c++11. Also irgendwie wird man es damals schon sinnvoll nutzen gekonnt haben.



  • ..PS: Kann es sein, dass ich alignof() nur im Zusammenhang mit alignas() benötige?



  • Wenn du nen Alignment-Crash haben möchtest, hier ne kleine Demo (für x86-Architektur):

    #include <iostream>
    #include <xmmintrin.h>
    
    int main()
    {
        alignas(16) float result[] = { 0.0f, 0.0f, 0.0f, 0.0f },
            a[] = { 1.0f, 1.0f, 1.0f, 1.0f },
            b[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    
        _mm_store_ps(result, _mm_add_ps(_mm_load_ps(a), _mm_load_ps(b)));
    
        for (auto x : result)
            std::cout << x << std::endl;
    
        return 0;
    }
    

    Wenn du das alignas(16) rausnimmst, und nicht extrem viel Glück hast, sollte das Programm vor die Wand laufen.

    0xMöbius schrieb:

    ..PS: Kann es sein, dass ich alignof() nur im Zusammenhang mit alignas() benötige?

    Nein. Gerade wenn du irgendwas mit Placement new machst, könnte es sein, dass du ein Objekt irgendwo in einem Speicherbereich platzieren möchtest,
    der an einer beliebigen Adresse beginnt. Dann macht es Sinn unter Verwendung von alignof() zu berechnen, wo genau du das Objekt platzieren musst,
    so dass du das Alignment einhältst. Da muss nirgendwo ein alignas() stehen.

    Finnegan



  • Hmhmhm. Ich glaube, mir fehlt das Wissen, nach welchen Regeln Objekte platziert werden.
    Wie kann ich denn (nur mit c++ und ohne c++11) dafür sorgen, dass ein Objekt entsprechend der Vorliebe des Prozessors ausgerichtet wird, und wie kann ich es verhindern? Placement new existiert doch nicht erst seit c11 ?

    Ähhmm ist der Datentyp für die CPU-Busbreite (richtiger Begriff?) der eines bel. Zeigers (oder nur void*, oder garnicht..)??
    Dann muss ich doch nur dafür sorgen, dass z.B. mein "T[]" das Alignment eines Ptrs hat, oder!?
    ..Oder muss ich dafür sorgen, dass das Array das Alignment des Ts hat?
    Warum gibt es überhaupt Alignment > CPU-Vorliebe ??

    Wäre es nicht irgendwie praktisch, wenn auch Arrays von Typen, deren Größe kleiner als die Busbreite ist, an der Busbreite ausgerichtet werden ( ..z.B. ein "char[sizeof(T)*N]"... )?! Und ist das evtl auch so, oder viellecht, oder nur bei x86, oder..?!



  • 0xMöbius schrieb:

    Hmhmhm. Ich glaube, mir fehlt das Wissen, nach welchen Regeln Objekte platziert werden.

    Welches Alignment verschiedene Datentypen benötigen ist von der Prozessorarchitektur abhängig.
    Insofern sollte der Compiler eigentlich am besten darüber Bescheid wissen und die Objekte automatisch
    richtig platzieren bzw. via alignof() den entsprechenden Wert zurückliefern.
    Wenn man keine exotischen Sachen macht wie Placement new oder wie in meinem Beispiel SIMD-Operationen verwendet,
    braucht einen das Alignment im Allgemeinen auch nicht zu interessieren, das ist der Job des Compilers.
    Falls man es doch mal manuell machen muss, so muss man sicherstellen, dass sich das Objekt an einer Adresse befindet,
    die ein Vielfaches ihres Alignments ist, also für ein Objekt vom Typ T muss gelten:

    reinterpret_cast<std::uintptr_t>(&object) % alignof(T) == 0

    Das sind aber alles keine alltäglichen C++-Probleme, sowas braucht man nur wenn man sehr spezielle Dinge macht (!) 😉

    0xMöbius schrieb:

    Wie kann ich denn (nur mit c++ und ohne c++11) dafür sorgen, dass ein Objekt entsprechend der Vorliebe des Prozessors ausgerichtet wird, und wie kann ich es verhindern? Placement new existiert doch nicht erst seit c11 ?

    Wie gesagt, meistens muss man nichts machen. Falls doch, gibt es ohne C++11 auch noch compiler-spezifische Attribute, die dasselbe machen,
    z.B. __attribute__((aligned(alignment))) (GCC) oder __declspec(align(alignment)) (MSVC). Auch gibt es oft systemspezifische Routinen,
    um sich (ziemlich low-level) Speicher mit pasendem Alignment zu holen wie posix_memalign() unter Linux oder _aligned_malloc() in MSVC.
    Wenn alle Stricke reißen, reserviert man einen Speicherblock der groß genug ist und platziert das Objekt nicht am Anfang des Blocks,
    sondern verschiebt es um ein paar Bytes, so dass das Alignment passt.

    0xMöbius schrieb:

    Ähhmm ist der Datentyp für die CPU-Busbreite (richtiger Begriff?) der eines bel. Zeigers (oder nur void*, oder garnicht..)??
    Dann muss ich doch nur dafür sorgen, dass z.B. mein "T[]" das Alignment eines Ptrs hat, oder!?
    ..Oder muss ich dafür sorgen, dass das Array das Alignment des Ts hat?
    Warum gibt es überhaupt Alignment > CPU-Vorliebe ??

    Wäre es nicht irgendwie praktisch, wenn auch Arrays von Typen, deren Größe kleiner als die Busbreite ist, an der Busbreite ausgerichtet werden ( ..z.B. ein "char[sizeof(T)*N]"... )?! Und ist das evtl auch so, oder viellecht, oder nur bei x86, oder..?!

    Die Variable in der du deinen Pointer auf die T s speicherst, muss Pointer-Alignment haben. Der Pointer selbst muss allerdings auf eine Adresse verweisen,
    die das Alignment von T hat (alignof(T*) == Pointer-Alignment, alignof(T) == T-Alignment). Und ansonsten: Ganz schön viele Fragen. Vielleicht liest du dich mal
    selbst weiter in das Thema ein, wenn es dich so sehr interessiert. Wikipedia ist bei technischen Themen immer ein guter Einstiegspunkt, danach kann man sich
    über die External Links weiterhangeln 😉

    Finnegan



  • Soooo, erstmal vielen Dank!

    Das sind aber alles keine alltäglichen C++-Probleme, sowas braucht man nur wenn man sehr spezielle Dinge macht (!) 😉

    Du meinst mit heißen Kohlen jonglieren, ein Rad erneuern, oder sogar Ketzerei betreiben ?! 😉

    Und ansonsten: Ganz schön viele Fragen.

    Wer nicht fragt, der nicht gewinnt..

    Ich habe mich gestern Abend/Nacht noch weiter damit beschäftigt. Ist ja eigentlich ganz einfach - wenn man erstmal weiß was genau damit gemeint ist.. Ich meine auch zu wissen, was man diesbezüglich vor c++11 gemacht hat / machen musste: GarniX, da Ausnahmeregelungen bestanden/bestehen. D.h. so wie ich das sehe, war meine ursprüngliche Sorge einfach unbegründet, da der Ältestenrat in seiner grenzenlosen Weisheit sich meiner Probleme angenommen, bevor ich überhaupt von ihnen gewusst 😃 .

    PS.
    Ich bin während meiner Suche auch beim Bjarne vorbeigesurft. Der erklärt mir, warum man kein "final"-keyword braucht (wohl schon etwas älter..). Ich habe mich offenbar inzwischen so an die c++-typischen workarounds gewöhnt, dass ich ihm zustimmen muss.
    Scheint so, als wäre das Wort nur eingeführt worden, um Methoden vor dem Überschreiben zu schützen; dafür ist mir jedenfalls auf Anhieb kein Workaround bekannt. Mir hätte da auch eine Syntax a la "= 0", ..z.B. "= 1" gereicht. Aber extra dafür ein neues Wort einzuführen - die spinnen, die Götter.. 😉
    Wieder ein Wort was meine IDE mir einfärbt, obwohl ich es garnicht als Keyword nutzen möchte - das wäre mit Brainfuck nicht passiert..
    Und irgendwie bleibt auch aus, dass man sich fast wie ein Hacker fühlt, wenn man eigentlich einfache Probleme umständlich lösen muss.

    MfG
    0xMöbius



  • Arcoth schrieb:

    Nimm stattdessen

    T* t = (T*) new std::aligned_storage_t<sizeof(T)*N, alignof(T)>;
    new (t+n) T; // Sollte wohl nicht t[n] sein
    

    Ich möchte drauf hinweisen, dass das _t bei aligned_storage_t hier wichtig ist und es sonst aligned_storage<...>::type heißen müsste, wenn es aligned_storage_t nicht gibt. Das kam nämlich erst mit C++14, wenn ich mich richtig erinnere.


Anmelden zum Antworten