Geschwindigkeit bei Schreiben auf Speicher


  • Mod

    Sewing schrieb:

    wieso ist 4000 die falsche Längenangabe und wieso tut de untere Möglichkeit nichts?

    memcpy s dritter Parameter repräsentiert die Byteanzahl. Ich bezweifle stark, dass int auf irgendeiner Maschine 1 Byte lang ist (auch wenn das prinzipiell möglich wäre, da wir von einem C++-Byte sprechen).

    meinst du reserve oder resize?

    Ich meine reverse fürs reservieren. Die Größe kannst du auch im Konstruktor angeben. Wobei ich wie erwähnt die Iterator-Paar Variante ausprobieren würde.

    Und noch ein Tipp: Ich würde mir um solche Dinge erstmal keine Gedanken machen. Eigne dir idiomatisches C++ an, dann wird dein Code in vielen Fällen auch optimal schnell werden (da idiomatisches C++ i.d.R. auch hinsichtlich Performance optimal ist). Falls deine Anwendung dann zu langsam ist, profilierst du sie. Deine frühreife "Optimierung" mit mempcy ist ja irgendwie schon schief gegangen.


  • Mod

    memcpy rechnet in Byte. Es weiß, wie so ziemlich alle C-Funktionen, nichts von den unterliegenden Typen. Dein Beispiel memcpy kopiert also wesentlich weniger als du denkst.

    Nimm, wie schon gesagt wurde, std::copy, wenn du einen memcpy-artigen Effekt in C++ möchtest. Da können solche Fehler nicht passieren und es ist genau so schnell, und es kommt auch mit komplexen Datentypen zurecht.

    Oder, wie auch schon gesagt, wenn es darum geht, eine Datenstruktur zu initialisieren, dann mach es direkt, ohne copy.



  • Ok Danke, werde ich mir zu Herzen nehmen.

    das ganze soll allerdings in einer Methode stattfinden von einer Klasse, die nen member std::vector<int> hat. Das heißt, ich kann nicht den ctor für eine iterator range verwenden, sondern nur deren assign-variante. Taugt das auch?

    Ich weiß leider apriori nicht, wieviele Werte der vector später hat


  • Mod

    Sewing schrieb:

    das ganze soll allerdings in einer Methode stattfinden von einer Klasse, die nen member std::vector<int> hat. Das heißt, ich kann nicht den ctor für eine iterator range verwenden, sondern nur deren assign-variante. Taugt das auch?

    Ja.



  • Also drei Möglichkeiten,

    std::vector<uint16_t> _AplitudeImg{};
    
      void setAmplitudeImg(uint16_t* data) {
        _AmplitudeImg.assign(data, data + 4000);
      }
    
    std::vector<uint16_t> _AplitudeImg{};
    
      void setAmplitudeImg(uint16_t* data) {
        std::copy(data, data+4000, _AmplitudeImg.begin());
    }
    
    std::vector<uint16_t> _AplitudeImg{};
    
      void setAmplitudeImg(uint16_t* data) {
        memcpy(_AmplitudeImg.data(), data, sizeof(uint16_t));
    }
    

    korrekt?

    macht es Sinn, vorher den vector auf irgendeinen Wert zu reserven im constructor?

    Gibt es eine Möglichkeit einen leeren container mit einer bestimmten capazität anzulegen? Denn wenn ich schreibe

    std::vector<int> v(4000)
    

    habe ich ja direkt 4000 value-initialized integer, die ich später sowieso wieder reassigne

    oder gibt es es wirklich nur

    std::vector<int> v{};
    v.reserve(4000);
    

  • Mod

    1. Du musst den vector vor dem copy entsprechend vergrößern, à la v.resize(4000) .
    2. reserve macht Sinn, wenn der vector unmittelbar danach wiederholt (bspw. durch viele push_back s) auf einen bekannten oder gut abschätzbaren Wert vergrößert wird.



  • warum resize und nicht reserve? Ich kopiere doch sowieso andere Werte unmittelbar danach in den Container


  • Mod

    Lies dir doch endlich mal die Konstruktoren von vector durch! Das wurde schon mehrmals in diesem Thread gesagt.

    std::vector<uint16_t> _AplitudeImg(data, data + 4000);
    

    Da! Eine Zeile, so schnell wie es geht, keine Möglichkeit, irgendetwas falsch zu machen.



  • wenn du meinen Beitrag gelesen hättest, dann wüsstest du, dass ich keine Möglichkeit habe, den container direkt zu initialisieren sondern das über ne member function tun muss


  • Mod

    @SeppJ: Sein vorletzter Beitrag sagt doch unmissverständlich, dass eine Memberfunktion den Container neu befüllen soll. Lesen musst auch Du. 🙂


  • Mod

    SeppJ schrieb:

    Lies dir doch endlich mal die Konstruktoren von vector durch! Das wurde schon mehrmals in diesem Thread gesagt.

    std::vector<uint16_t> _AplitudeImg(data, data + 4000);
    

    Da! Eine Zeile, so schnell wie es geht, keine Möglichkeit, irgendetwas falsch zu machen.

    Arcoth schrieb:

    @SeppJ: Sein vorletzter Beitrag sagt doch unmissverständlich, dass eine Memberfunktion den Container neu befüllen soll. Lesen musst auch Du. 🙂

    Kann man ja ganz einfach anpassen

    _AplitudeImg = decltype(_AplitudeImg)(data, data + 4000);
    


  • Arcoth schrieb:

    Du kannst dieselbe Performance erreichen, wenn du den vector zuvor auf 4000 resized und dann std::copy verwendest,

    Nicht ganz, da muss ja erstmal alles mit 0 vollgeschrieben werden und danach kommt erst das Kopieren. memcpy auf un-initialisierten Speicher ist schneller. Und dass der Compiler erkennt dass das unnötig ist und es wegoptimieren kann, davon würde ich nicht ausgehen.

    Arcoth schrieb:

    oder wahrscheinlich auch wenn du den Iterator-paar Konstruktor einsetzt (da wird intern wahrscheinlich das gleiche, optimierte Funktionstemplate aufgerufen).

    Genau. Oder halt .assign(begin, end) wenn man den vector schon hat.

    Ich weiss nicht wie gut es die aktuellen Libraries/Compiler wirklich optimieren können, aber das ist mMn. die Variante die am besten optimiert werden sollte. Die Library könnte hier sehr einfach ne Spezialisierung für memcpy-kompatible Typen haben und dann wirklich nur mehr sinngemäss malloc + memcpy machen, ohne unnötige Zwischenschritte.



  • @hustbaer: muss ich den vector mit reserve vorher allozieren umd danach mit assign bzw nem constructor mit der iterator range zu kopieren oder geht das auch mit nem defaukt-initialized vector?

    und auf der ersten Seite: Sind die drei von mir aufgeführten Möglichkeiten so korrekt? und wenn ja, sind die gleich performant?



  • Sewing schrieb:

    warum resize und nicht reserve? Ich kopiere doch sowieso andere Werte unmittelbar danach in den Container

    reserve + push_back ist nicht optimal, weil der Compiler vermutlich nicht schlau genug ist dabei den "ist nocht genug Platz" Check in push_back wegzuoptimieren. Dazu müsste er sehen können dass auf Grund des reserve davor der Test nie negativ ausgehen kann, und das ist was was ich als extrem schwer für den Compiler einschätzen würde. So schwer dass ich schätzen würde dass kein aktueller Compiler es schafft. Verglichen mit 1x memcpy für alle 4000 Elemente wird das dadurch ordentlich viel langsamer werden. Wenn man komplexere Elemente einfügt ist dann allerdings schnell der Punkt erreicht wo der prozentuelle Unterschied sehr klein wird.

    Was du machen kannst ist v.reserve(elementCount) + v.insert(v.begin(), elementsBegin, elementsEnd) . Bzw. eben v.reserve(elementCount) + v.assign(elementsBegin, elementsEnd) wenn der vector davor sowieso leer ist. Das reserve davor sollte nicht nötig sein, aber ich vermute dass es laut Standard nicht vorgeschrieben ist dass vector das selbst macht, und wenn du sicher gehen willst kannst du es explizit hinschreiben.


  • Mod

    hustbaer schrieb:

    Arcoth schrieb:

    Du kannst dieselbe Performance erreichen, wenn du den vector zuvor auf 4000 resized und dann std::copy verwendest,

    Nicht ganz, da muss ja erstmal alles mit 0 vollgeschrieben werden und danach kommt erst das Kopieren. memcpy auf un-initialisierten Speicher ist schneller. Und dass der Compiler erkennt dass das unnötig ist und es wegoptimieren kann, davon würde ich nicht ausgehen.

    Man kann sich nicht darauf verlassen, aber mindestens eine Implementierung ist ausgeklügelt genug:

    auto f(int* arr) {
        std::vector<int> v(1000);
        std::copy_n(arr, 1000, v.data());
        return v;
    }
    

    GCC Godbolt: https://godbolt.org/g/vgDA9d
    Clang 4.0 mit -O2:

    f(int*):                                 # @f(int*)
            push    r14
            push    rbx
            push    rax
            mov     r14, rsi
            mov     rbx, rdi
            xorps   xmm0, xmm0
            movups  xmmword ptr [rbx], xmm0
            mov     qword ptr [rbx + 16], 0
            mov     edi, 4000
            call    operator new(unsigned long)
            mov     qword ptr [rbx], rax
            lea     rcx, [rax + 4000]
            mov     qword ptr [rbx + 16], rcx
            mov     qword ptr [rbx + 8], rcx
            mov     edx, 4000
            mov     rdi, rax
            mov     rsi, r14
            call    memcpy
            mov     rax, rbx
            add     rsp, 8
            pop     rbx
            pop     r14
            ret
    

    GCC rafft es leider nicht (siehe das verlinkte Fenster, -O3 zeigt immer noch rep stosq zum Ausnullen). Selbes gilt für ICC. Bei VC++ erkenne ich nichts mehr, der Assembler ist irgendwie ungekürzt.



  • Sewing schrieb:

    @hustbaer: muss ich den vector mit reserve vorher allozieren umd danach mit assign bzw nem constructor mit der iterator range zu kopieren oder geht das auch mit nem defaukt-initialized vector?

    Wenn du sicher gehen willst dass es ohne reserve optimal läuft, dann step durch den Code und/oder profile es. Ansonsten: schreib das reserve davor um sicherzugehen.

    Sewing schrieb:

    und auf der ersten Seite: Sind die drei von mir aufgeführten Möglichkeiten so korrekt? und wenn ja, sind die gleich performant?

    Also unter der Annahme dass es 4000 uint16_t sind (und nicht 4000 Byte)...

    Sewing schrieb:

    std::vector<uint16_t> _AplitudeImg{};
    
      void setAmplitudeImg(uint16_t* data) {
        _AmplitudeImg.assign(data, data + 4000);
      }
    

    Korrekt. Optimal unter der Annahme dass vector schlau genug ist und selbst passend reserve() d.

    Sewing schrieb:

    std::vector<uint16_t> _AplitudeImg{};
    
      void setAmplitudeImg(uint16_t* data) {
        std::copy(data, data+4000, _AmplitudeImg.begin());
    }
    

    Falsch. Du musst vor dem copy() noch die Elemente erzeugen, copy() selbst erzeugt keine sondern überschreibt sie nur (assignment operator). Also vorher resize() machen. Alternativ könnte man einen back_insert_iterator verwenden, aber da würde ich wetten dass das wieder ordentlich langsamer ist.

    Sewing schrieb:

    std::vector<uint16_t> _AplitudeImg{};
    
      void setAmplitudeImg(uint16_t* data) {
        memcpy(_AmplitudeImg.data(), data, sizeof(uint16_t));
    }
    

    Auch falsch. Muss memcpy(_AmplitudeImg.data(), data, sizeof(uint16_t) * 4000) heissen wenn du 4000 Elemente kopieren willst. Und auch hier wieder: memcpy erzeugt dir keine Elemente, es kopiert bloss bytes. Also auch hier ein resize() vorher nötig.

    Sewing schrieb:

    macht es Sinn, vorher den vector auf irgendeinen Wert zu reserven im constructor?

    mMn. ja. Entweder im Konstruktor oder in setAmplitudeImg() . Wenn eh schon genug Platz ist ist reserve sehr billig, kann man also ruhig in setAmplitudeImg() machen. Wenn öfter mal Objekte konstruiert werden auf die dann nie setAmplitudeImg() aufgerufen wird, würde ich sogar wärmstens empfehlen es nur in setAmplitudeImg() und nicht im Konstruktor zu machen.

    Sewing schrieb:

    Gibt es eine Möglichkeit einen leeren container mit einer bestimmten capazität anzulegen? Denn wenn ich schreibe (...)
    oder gibt es es wirklich nur

    std::vector<int> v{};
    v.reserve(4000);
    

    Ich kenne nur diese Variante.

    Wenn der Platz für die 4000 Elemente in dem Objekt immer oder fast immer benötigt wird, und es selten (oder nie) mehr werden, kannst du allerdings boost::small_vector<int, 4000> verwenden. Der hat dann Platz für 4000 Elemente direkt eingebaut. Bzw. wenn es immer genau 4000 Elemente sind kannst du natürlich auch std::array<int, 4000> verwenden. Bzw. unique_ptr<std::array<int, 4000>> wenn manchmal gar keine und ansonsten immer genau 4000 benötigt werden.



    std::vector<uint16_t> v(1000);
    
      void foo(uint16_t* data) {
        v.assign(data, data + 1000);
      }
    
    std::vector<uint16_t> v(1000);
    
      void foo(uint16_t* data) {
        std::copy(data, data+1000, v.begin());
    }
    
    std::vector<uint16_t> v(1000);
    
      void foo(uint16_t* data) {
        memcpy(v.data(), data, 4000*sizeof(uint16_t));
    }
    
    std::vector<uint16_t> v(1000);
    
     void foo(uint16_t* data) {
        std::copy_n(arr, 1000, v.data()); 
    }
    
    std::vector<uint16_t> v(1000);
    
     void foo(uint16_t* data) {
        v.insert(v.begin(), data, data+1000)
    }
    

    Alle Varianten korrekt?



  • Danke für die Erläuterungen : )

    Was genau ist der vorteil von std::array gegenüber std::vector?

    Dachte, dass array default auf dem stack erzeugt wird, wenn ich aber den aber mit nem smart ptr auf dem Heap erzeuge, ist diese Tatsache aber auch nicht mehr gegeben



  • Sewing schrieb:

    Was genau ist der vorteil von std::array gegenüber std::vector?

    Der Vorteil ist dass es viel weniger kann. Daher benötigt es weniger Code. Weniger Arbeit für den Optimizer und geringere Chancen dass Dinge nicht wegoptimiert werden können.

    Und wenn die Grösse (compile-time) konstant ist, dann dokumentiert std::array diesen Umstand auch gleich schön.

    Sewing schrieb:

    Dachte, dass array default auf dem stack erzeugt wird, wenn ich aber den aber mit nem smart ptr auf dem Heap erzeuge, ist diese Tatsache aber auch nicht mehr gegeben

    std::array enthält direkt die Elemente, gleich wie ein "nacktes" Array. D.h. die Elemente liegen dort wo auch das std::array liegt. Wenn du std::array als Membervariable einer Klasse X verwendest, und du Objekte der Klasse X auf dem Stack erzeugst, dann liegen die std::array Elemente auf dem Stack. Erzeugst du die Objekte der Klasse X allerdings mit new , dann liegen die Elemente von std::array natürlich ebenso wie das ganze X Objekt im "free store" (heap).



  • Arcoth schrieb:

    Man kann sich nicht darauf verlassen, aber mindestens eine Implementierung ist ausgeklügelt genug:

    Cool. Bin beeindruckt. 🙂


Anmelden zum Antworten