Socket-Buffer C vs C++



  • @knivil:

    Wenn ich also mein Array in einem Template vergrabe, wie es vector macht, ist es C++ und wenn ich meinen Speicher direkt selbst mit new allokiere und zerstöre ( oder als konstantes Array anlege ), ist es kein C++ ?



  • knivil schrieb:

    Ich habe keine Messwerte gesehen. Meine Erwartungshaltung war, dass sie fettgedruckt irgendwo hervorleuchten. Genauso wie man Behauptungen ins Internet hinausschreit, sollte auch der eigene Irrtum bekanntgegeben werden.

    Ich habe geschrieben, dass jetzt beide Varianten eine identische Laufzeit aufweisen. Musst nur mal alle Beiträge lesen und nicht nur ausgewählte. 😉

    Zudem wollte ich nicht behaupten, sondern ich wollte explizit Hilfe, um meine derzeitige C-Lastigkeit im Bereich Sockets noch etwas einzudämmen und ich wollte dabei sichergehen, dass ich mir da kein Performance-Ei ins Nest lege.
    Außerdem bin ich halt bestrebt, die Kritik anzunehmen und auch, sofern performancetechnisch einwandfrei, C++ intensiver einzusetzen. Ich merke ja selbst bei meinem Quellcode, dass der C++-Anteil nicht immer so hoch ist, wie er sein könnte. Ich arbeite halt viel mit Buffern und co. und da ist C meist näher als C++. Und genau diesen Umstand will ich beseitigen, indem ich in mir selbst die Überzeugung nähre, dass C++ in dem Fall keinen Performancenachteil mit sich bringt.



  • Wenn ich also mein Array in einem Template vergrabe, wie es vector macht, ist es C++ und wenn ich meinen Speicher direkt selbst mit new allokiere und zerstöre ( oder als konstantes Array anlege ), ist es kein C++ ?

    Wie kommst du darauf, beides ist C++, nur nutze ich bei einem RAII. Du must RAII in C++ nicht benutzen, aber das ist der Unterschied.

    Musst nur mal alle Beiträge lesen und nicht nur ausgewählte.

    Das habe ich nicht gemeint. Du must meine Posts verstehen. Mir aber voellig egal.



  • Was ich immernoch nicht verstehe und Cookie wohl auch nicht, ist die Möglichkeit, ein konstantes Char-Array als Member in einer Klasse ( oder halt auf dem Stack ) anzulegen und darauf zu arbeiten? Wieso ist die Variante aus deiner Sicht schlechter als die vector<char>-Variante?



  • @It0101 Mach dir mal keine Sorgen um Arrays auf dem Stack. (Außer wenn sie wie hier 1 MB groß sind :D) Aber wenn dein Code C-Lastig ist dann solltest du einfach mal durch gehen, und an jeder Stelle an der ein Handle (z.B. FILE*, oder auch char* auf reservierten Speicher, oder auch SOCKET) wieder frei gegeben wird diese Freigabe in einen Destruktor schieben.



  • cooky451 schrieb:

    @It0101 Mach dir mal keine Sorgen um Arrays auf dem Stack. (Außer wenn sie wie hier 1 MB groß sind :D) Aber wenn dein Code C-Lastig ist dann solltest du einfach mal durch gehen, und an jeder Stelle an der ein Handle (z.B. FILE*, oder auch char* auf reservierten Speicher, oder auch SOCKET) wieder frei gegeben wird diese Freigabe in einen Destruktor schieben.

    Das mach ich sowieso schon. In der Hinsicht gibts doch auch gar keine Probleme.
    Ich habe mich nur deswegen mal damit auseinandergesetzt, weil hier im Forum immer gleich rumgeheult wird, von wegen: "du darfst das so nicht machen, dass ist kein C++", "new benutzt man nicht, weil man immer Container benutzt", usw... Genau deswegen hab ich mich hingesetzt und überlegt, wie ichs besser machen kann.



  • Wieso ist die Variante aus deiner Sicht schlechter als die vector<char>-Variante?

    Abhaengig vom Anwendungszweck. Allgemein beim Array konstanter Laenge als Member kann ich nicht "moven". Eine Kopie ist noetig, ob mit memcpy oder std::copy ist egal (ich nutze letzteres). Obwohl meist ausreichend flink, ist es kein Vergleich zum Verschieben eines Pointers.

    Ich arbeite halt viel mit Buffern und co. und da ist C meist näher als C++

    Ich auch.

    Benchmark?

    Hier bei mir, aus meiner Erfahrung, bei meinem Problem, ... wenn du Sourcecode sehen moechtest, hier:

    void label::buffer::extract(span::buffer const& buf, label l, span::buffer& out_buf) const
    {
        out_buf.lines_.resize(buf.lines().size());
        out_buf.spans_.clear();
    
        auto pline = buf.lines().data();
        auto pline_end = pline + buf.lines().size();
        auto pline_out = out_buf.lines_.data();
        auto pspan = buf.spans().data();
        auto plabel = labels().data();
    
        for (; pline < pline_end; ++pline)
        {
            auto old_size = out_buf.spans().size();
            auto const pspan_end = pspan + *pline;
            for (; pspan < pspan_end; ++pspan, ++plabel)
            {
                if (*plabel == l) out_buf.spans_.push_back(*pspan);
            }
            *pline_out++ = out_buf.spans().size() - old_size;
        }
    }
    

    Aber ich werde ihn nicht in ein nachvollziehbaren Benchmark aufbereiten.

    RAII bei Arrays auf dem Stack, klingt ja interessant.

    Da steht char-Array, ein Vergleich mit std::vector macht nur Sinn, wenn es dynamisch ist.



  • It0101 schrieb:

    ...

    Vermutlich mache ich ja etwas falsch... Bei mir läuft der C++ Code genauso schnell wie der C-Code. Und das auch wenn ich über einen Index zugreife (Getestet unter Visual Studio 2012).

    #include <iostream>
    #include <array>
    #include <windows.h>
    
    const unsigned Size = 1048576;
    const unsigned Cnt = 1000;
    
    void TestCPP()
    {
        std::array<char, Size> Buffer;
    
        long t1 = GetTickCount();
    
        for(unsigned k = 0; k < Cnt; ++k)
            for(unsigned i = 0; i < Size; ++i)
                Buffer[i] = (char)( i % 26 + 65 );
    
        long t2 = GetTickCount();
        std::cout << "C++: " << t2 - t1 << std::endl;
    }
    ...
    


  • It0101 schrieb:

    Das mach ich sowieso schon. In der Hinsicht gibts doch auch gar keine Probleme.

    Sehr gut.

    It0101 schrieb:

    Ich habe mich nur deswegen mal damit auseinandergesetzt, weil hier im Forum immer gleich rumgeheult wird, von wegen: "du darfst das so nicht machen, dass ist kein C++", "new benutzt man nicht, weil man immer Container benutzt",

    Ja, nur leider hast du hier nicht new mit vector verglichen (der Vergleich macht zumindest begrenzt Sinn), sondern ein Array auf dem Stack mit vector (der Vergleich macht keinen Sinn).



  • Gut, war mein Fehler. Ich hoffe trotzdem, dass ich den Unterschied RAII zwischen C Code in C++ und C++ Code vermitteln konnte. Gewiss gibt es mehr, aber fuer mich ist halt RAII der Knackpunkt. Ich kenne keine andere Sprache, die es aehnlich handhabt.

    Das Rumgeheule kommt daher, dass viele mit Java,C#,C, ... Hintergrund anfangen C++ zu programmieren. Was sie aber nicht tun, da nicht nur Sprachmittel (malloc gegen new) ausgetauscht werden muessen sondern auch Konzepte.



  • Gut, dann sind wir uns ja im Wesentlichen einig. Ich danke euch erstmal für eure Impressionen. Ich werde mal über den vector nachdenken.



  • It0101 schrieb:

    Gut, dann sind wir uns ja im Wesentlichen einig. Ich danke euch erstmal für eure Impressionen. Ich werde mal über den vector nachdenken.

    Wieso beißt du dich eigentlich am std::vector fest, wenn du ein std::array willst (Zumindest deinem C-Code nach, siehe auch dem Code von meinen vorherigen Kommentar)?



  • Da hab ich mich vielleicht etwas doof ausgedrückt.

    Ich wollte eher sagen: "Ich werde die C++-Variante in Zukunft stärker berücksichtigen". Ich beiß mich nicht an vector fest. Array hab ich auch im Hinterkopf. 😉



  • Du darfst natürlich auch in C++ lowlevel programmieren. Es gehört bei C++ zum guten Ton das Ganze wegzukapseln und aufzupassen dass es mit (erkennbar) falschem Input nicht merkwürdig crasht.



  • cooky451 schrieb:

    Ausgabe:

    vector: 1.06401
    vector: 1.06201
    vector: 1.06201
    malloc: 0.0220203
    malloc: 0.0210204
    malloc: 0.0210201
    raw new: 0.022021
    raw new: 0.0220209
    raw new: 0.0210198
    raii new: 0.0210201
    raii new: 0.0210202
    raii new: 0.0210198
    

    Der Unterschied ist, dass der std::vector den Speicher initialisiert, indem er für jedes Element seinen default-Konstruktor aufruft, was in der Regel ein sehr gutes Feature ist.

    Der std::vector führt 1048577 Zuweisungen pro Schleifendurchlauf aus. Die anderen Tests aber nur eine einzige. Ergänze ich im test_malloc eine Zuweisung für alle Elemente, dann wird der test_malloc sogar langsamer.

    Und ja, wie Ethon schon gesagt hat, darf man in C++ auch low level programmieren, wo es Sinn macht. Sinnvoll ist es aber, solche Allokationen in eine Klasse zu kapseln. Man könnte ja auch so einen "raw_vector" oder so was implementieren, mit dem man genauso sicher arbeiten kann, wie mit einem std::vector.

    Das Problem bei malloc/free oder new/delete ist, dass man free bzw. delete leicht vergessen oder versehentlich durch ein throw oder return umgehen kann. Bei malloc kommt noch hinzu, dass man es wie im Beispiel eindrucksvoll gezeigt vergessen kann zu prüfen, ob das überhaupt geklappt hat. Es kommt ja keine Exception.



  • dass man free bzw. delete leicht vergessen

    Das ist nicht der Hauptgrund. Wer die Anzahl seiner new/deletes nicht ausgleicht, ist einfach fahrlaessig, schludrig oder unwissend und hat auch kein Interesse daran. Desweiteren gibt es genug Tools, die solche Fehler melden.



  • Ich würd jetzt eher "reserve" und "push_back"/"emplace_back" verwenden, als einen vector zu schreiben, der bei "resize" seine Elemente nicht initialisiert...



  • tntnet schrieb:

    Der Unterschied ist, dass der std::vector den Speicher initialisiert, indem er für jedes Element seinen default-Konstruktor aufruft, was in der Regel ein sehr gutes Feature ist.

    Stimmt, diese total schwachsinnige Regel hatte ich wohl in der Hoffnung verdränkt, dass sie dann aufhören würde zu existieren. Das ist allerdings keine default-initialization (welche sinnvoll wäre, und hier eben genau nichts tun würde), sondern eine value-initialization, was in unserem Fall zu einer zero-initialization wird.



  • Kann mir jemand erklären, wieso das nicht den erhofften Speedup bringt?]cpp]template <typename T>
    struct skip_default_construct_allocator : std::allocator<T> {
    template <class U, class... Args>
    void construct (U* p, Args&&... args)
    {
    static_cast<std::allocator<T>&>(this).construct(p, std::forward<Args>(args)...);
    }
    template <class U>
    void construct (U
    p)
    {
    }
    };

    void test_uninitialized_vector(std::size_t size, unsigned count)
    {
    default_stopwatch sw;

    while (count--) {
    std::vector<char,
    skip_default_construct_allocator<char> > buffer(size);
    buffer[(count * 1234567) % size] = 77;
    }

    std::cout << "uninitialized vector: " << duration_cast<double>(sw.elapsed()) << '\n';
    }
    [/cpp]



  • Vermutlich weil der vector nicht allocator_type::construct verwendet sondern placement new.

    Du könntest dir ein uninitialized_value Wrapper-Template schreiben das im Default-Konstruktor garnichts macht und ein Bytearray verwendet um bei Bedarf ein Objekt konstruieren zu können. (std::aligned_storage)


Anmelden zum Antworten