Socket-Buffer C vs C++



  • 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)



  • cooky451 schrieb:

    hustbaer schrieb:

    Klar ist er richtig. Er ist halt nur unnötig.

    Nicht ganz. 1. ermöglicht es in vielen move-Fällen erst die Optimierung den Destruktor auszulassen (das habe ich in einem anderen C vs C++ Thread hier festgestellt), und 2. ist es generell performanter so oft wie möglich ein call auszulassen.

    In C++11 ist klar gestellt, dass der Compiler den Aufruf der Deallokationsfunktion unterlassen darf, wenn ein Nullzeiger vorliegt (das "no effect" aus C++03 war ja ziemlich unklar).
    Ich mag die Prüfung auch, aber aus logischen Gründen: was nicht existiert, kann auch nicht gelöscht werden. Der Code wäre auch dann noch korrekt, wenn Nullzeiger nicht mit delete verwendet werden dürften. Es ist nicht klar, wieso die bloße Tatsache, dass Nullzeiger mit delete doch benutzt werden dürfen, einen Imperativ dahin schafft, das die Abfrage plötzlos falsch ist (hat hustbaer auch nicht behauptet, aber das ist im Forum der allgemeine Trend bei diesem Thema).



  • knivil schrieb:

    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.

    Wenn das nur immer so einfach wäre. Gerade bei der Fehlerbehandlung sehe ich dort häufig Unterlassungen. Zwischen new und delete werden Funktionen aufgerufen, die eine exception werfen könnten. Da wird leicht vergessen, das delete in einem catch Block zu machen. Unter Umständen muss man überhaupt erst mal ein try-catch in die Funktion einführen, um überhaupt einen catch Block zu haben. Und auch Tools muss man zum einen einsetzen und auch noch den Fall Testen, wo der Memoryleak überhaupt auftritt. Fehler sind ja in der Regel eher selten. Daher heißen sie ja auch "exception".

    char* buffer = new char[8192];
    unsigned count = socket.read(buffer, 8192);
    doSomethingWithReceivedData(buffer, count);
    delete[] buffer;
    

    Sieht sehr übersichtlich aus und der nicht so versierte Entwickler könnte meinen, das sei richtig. Aber ich muss euch wohl nicht sagen, was hier falsch gelaufen ist.



  • Nur Müslifresser benutzen Exceptions.



  • überlauf schrieb:

    Nur Müslifresser benutzen Exceptions.

    Entweder ca. 50% des Codes für Fehlerhochreichungen und passende close-Aufrufe selber coden ODER Exceptions benutzen. Mag jeder machen, wie er will. Exceptions sind zur Zeit auf PCs ein wenig schneller und brauchen nicht spürbar mehr RAM. Ist vollkommen egal, wenn ich mal anschaue, welchen schlechten Fremdcode ich auf dem Rechner habe und benutze, wo unpassende Algos verwendet werden.



  • @tntnet: Ausreden fuer Nachlaessigkeit. Wenn ich ein Messer benutze, muss mit angemessener Umsicht arbeiten, da es ihm egal ist, was es schneidet. Der Hauptgrund RAII zu nutzen ist Bequemlichkeit. Und ich mach es mir gern bequem.

    Aber ich muss euch wohl nicht sagen, was hier falsch gelaufen ist.

    Das haengt ganz davon ab, was socket.read und doSomething machen. Den 4 Zeilen kann ich keinen Fehler entnehmen, zeig ihn mir.



  • Ein wichtiger Grund für mich RAII für jegliche Art Cleanup zu benutzen ist, dass ich gar keinen try-Block haben will. Keinen try-Block zu haben ist nämlich sehr praktisch, z.B. wenn man Tests laufen lässt.
    Dann sieht der Debugger ne unhandled Exception und hält das Programm für mich an.
    Mit nem try{}catch(...){ ... ; throw; } steht man dann bloss dummerweise an der falschen Stelle.

    Wobei der wichtigste Grund natürlich ist: ich mag Code der leicht zu schreiben und leicht zu lesen ist. RAII hilft dabei enorm.



  • knivil schrieb:

    @tntnet: Ausreden fuer Nachlaessigkeit. Wenn ich ein Messer benutze, muss mit angemessener Umsicht arbeiten, da es ihm egal ist, was es schneidet. Der Hauptgrund RAII zu nutzen ist Bequemlichkeit. Und ich mach es mir gern bequem.

    Aber ich muss euch wohl nicht sagen, was hier falsch gelaufen ist.

    Das haengt ganz davon ab, was socket.read und doSomething machen. Den 4 Zeilen kann ich keinen Fehler entnehmen, zeig ihn mir.

    Nachlässigkeit und Bequemlichkeit sind doch im Prinzip nur andere Worte für Effizienz. Also ich meine die Effizienz des Entwicklers. Je weniger ich mich um Aufräumarbeiten kümmern muss, desto mehr nützlichen Code kann ich pro Zeiteinheit produzieren.

    socket.read und doSomething sind einfach Beispiele. Wenn ich RAII verwende, dann ist mir das egal, ob sie Exceptions werfen oder nicht.

    hustbaer schrieb:

    Ein wichtiger Grund für mich RAII für jegliche Art Cleanup zu benutzen ist, dass ich gar keinen try-Block haben will. Keinen try-Block zu haben ist nämlich sehr praktisch, z.B. wenn man Tests laufen lässt.
    Dann sieht der Debugger ne unhandled Exception und hält das Programm für mich an.
    Mit nem try{}catch(...){ ... ; throw; } steht man dann bloss dummerweise an der falschen Stelle.

    Wobei der wichtigste Grund natürlich ist: ich mag Code der leicht zu schreiben und leicht zu lesen ist. RAII hilft dabei enorm.

    Und so nebenbei leidet die Lesbarkeit des Codes, was die Fehlerwahrscheinlichkeit erhöht, da ich den Code nicht mehr so leicht überblicken kann.



  • Was soll das hier überhaupt, niemand hat jemals etwas gegen RAII gesagt... wer hat die besseren Argumente für RAII? 😕



  • cooky451 schrieb:

    Was soll das hier überhaupt, niemand hat jemals etwas gegen RAII gesagt...

    Wenn du willst:

    volkard schrieb:

    überlauf schrieb:

    Nur Müslifresser benutzen Exceptions.

    Entweder ca. 50% des Codes für Fehlerhochreichungen und passende close-Aufrufe selber coden ODER Exceptions benutzen. Mag jeder machen, wie er will. Exceptions sind zur Zeit auf PCs ein wenig schneller und brauchen nicht spürbar mehr RAM.

    Kommt drauf an...
    Wenn Exceptions wirklich einen Ausnahmefall darstellen, wo das Programm nicht sinnvoll weiterarbeiten kann dann sind exit oder Exceptions beide Recht.
    Wenn es ein unerwarteter Fehler ist, bei dem das Programm aber trotzdem sinnvoll reagieren muss, ja, dann sind Exceptions vielleicht angemessen. Ist mir aber noch nicht oft vorgekommen. ("Write programs that do one thing and do it well." Für megamonolitische Monsterprogramme stimmt das natürlich nicht)

    Bei Netzwerk-Zeug kann es sehr gut passieren, dass der Datenstrom korrupt ist oder sonstwas nicht mit dem Paket stimmt. 60% gut, 40% schlecht. Dann brauchen Exceptions viel mehr Zeit und viel mehr RAM.



  • tntnet schrieb:

    hustbaer schrieb:

    Ein wichtiger Grund für mich RAII für jegliche Art Cleanup zu benutzen ist, dass ich gar keinen try-Block haben will. Keinen try-Block zu haben ist nämlich sehr praktisch, z.B. wenn man Tests laufen lässt.
    Dann sieht der Debugger ne unhandled Exception und hält das Programm für mich an.
    Mit nem try{}catch(...){ ... ; throw; } steht man dann bloss dummerweise an der falschen Stelle.

    Wobei der wichtigste Grund natürlich ist: ich mag Code der leicht zu schreiben und leicht zu lesen ist. RAII hilft dabei enorm.

    Und so nebenbei leidet die Lesbarkeit des Codes, was die Fehlerwahrscheinlichkeit erhöht, da ich den Code nicht mehr so leicht überblicken kann.

    Durch RAII leidet die Lesbarkeit? Kann ich jetzt nicht nachvollziehen...



  • hustbaer schrieb:

    tntnet schrieb:

    hustbaer schrieb:

    Ein wichtiger Grund für mich RAII für jegliche Art Cleanup zu benutzen ist, dass ich gar keinen try-Block haben will. Keinen try-Block zu haben ist nämlich sehr praktisch, z.B. wenn man Tests laufen lässt.
    Dann sieht der Debugger ne unhandled Exception und hält das Programm für mich an.
    Mit nem try{}catch(...){ ... ; throw; } steht man dann bloss dummerweise an der falschen Stelle.

    Wobei der wichtigste Grund natürlich ist: ich mag Code der leicht zu schreiben und leicht zu lesen ist. RAII hilft dabei enorm.

    Und so nebenbei leidet die Lesbarkeit des Codes, was die Fehlerwahrscheinlichkeit erhöht, da ich den Code nicht mehr so leicht überblicken kann.

    Durch RAII leidet die Lesbarkeit? Kann ich jetzt nicht nachvollziehen...

    Ich auch nicht 😃 . Ich meinte, beim Verzicht auf RAII leidet die Lesbarkeit. Entschuldigt meine unpräzise Ausdrucksweise.


Anmelden zum Antworten