Doppelschleife verlassen - Sprungmarke vermeiden



  • seldon schrieb:

    Warum streitet ihr euch eigentlich in einem C++-Forum über alte Denkschulen der Fehlerbehandlung in C? Es steht doch außer Frage, dass kein C++-Programmierer mit mehr als zwei Wochen Erfahrung (sprich, der schonmal was von Exceptions gehört hat) vfs_readdir so aufbauen würde.

    Und zu dieser strstr-Implementation verliere ich besser kein Wort.

    ich dachte es ging unm "goto" und nicht die Fehlerbehandlung in C

    lg lolo



  • seldon schrieb:

    Es steht doch außer Frage, dass kein C++-Programmierer mit mehr als zwei Wochen Erfahrung (sprich, der schonmal was von Exceptions gehört hat) vfs_readdir so aufbauen würde.

    Mit C++ Exceptions sollte man sehr sparsam sein.
    http://msdn.microsoft.com/en-us/library/c0hwkhwe(VS.80).aspx



  • C++Fan 2010 schrieb:

    seldon schrieb:

    Es steht doch außer Frage, dass kein C++-Programmierer mit mehr als zwei Wochen Erfahrung (sprich, der schonmal was von Exceptions gehört hat) vfs_readdir so aufbauen würde.

    Mit C++ Exceptions sollte man sehr sparsam sein.
    http://msdn.microsoft.com/en-us/library/c0hwkhwe(VS.80).aspx

    blah
    blaaaaaaaaaaaaaaaaaaaaaaaah

    6, setzen



  • also hustbaer, beweise belege, erfahrungen o.ä. blah bringt uns alle nicht weiter 😉

    evtl. erninnere ich mal an das thread thema "Doppelschleife verlassen - Sprungmarke vermeiden"
    und nicht "C++ Exceptions"

    lg lolo



  • hustbaer schrieb:

    blah
    blaaaaaaaaaaaaaaaaaaaaaaaah

    Du wolltest mir was mitteilen?



  • noobLolo schrieb:

    also hustbaer, beweise belege, erfahrungen o.ä. blah bringt uns alle nicht weiter 😉
    evtl. erninnere ich mal an das thread thema "Doppelschleife verlassen - Sprungmarke vermeiden"
    und nicht "C++ Exceptions"

    Um eine Brücke zu schlagen: ich denke Freund hustbär würde die Schleife mittels einer geworfenen Exception verlassen. 😃 Das ist sicherlich nicht im Sinne des Erfinders.



  • Es ist zwar richtig, dass Exception-Handling einen gewissen Overhead erzeugt, allerdings bezieht sich dieser bei der üblichsten Form des Exception-Handlings (table drive) vor allem auf die Größe des Kompilats. Insbesondere ist der Laufzeitoverhead für Fälle, in denen keine Exception geschmissen wird, eher gering - was für einen Fehlerbehandlungsmechanismus ja auch Sinn macht.

    Dementsprechend ist exception-basierte Fehlerbehandlung keineswegs weniger performant, als nach jeder Aktion auf Fehler zu prüfen und ggf. irgendwohin zu springen, von Lesbarkeit ganz zu schweigen. Deshalb bleibe ich dabei, dass kein C++-Programmierer, der die Sprache auch nur ansatzweise versteht, vfs_readdir so aufbauen würde.

    Was das Springen aus der Schleife angeht, ist eine Exception allerdings in der Regel wohl wirklich keine sinnvolle Methode. In den meisten Fällen dürfte es sinnvoller sein, die innere Schleife in eine eigene Funktion auszulagern, aber das muss im Einzelfall entschieden werden. Es mag Fälle geben, in denen goto eine vertretbare Möglichkeit ist, allerdings sind die damit verbundenen Eigenheiten (beispielsweise in Bezug auf Objektlebenszeiten) derart haarig, dass ich schon etwas Hirnschmalz darauf verwenden würde, es zu vermeiden.



  • seldon schrieb:

    Dementsprechend ist exception-basierte Fehlerbehandlung keineswegs weniger performant, als nach jeder Aktion auf Fehler zu prüfen und ggf. irgendwohin zu springen, von Lesbarkeit ganz zu schweigen.

    Teilweise richtig. Aber wenn Du eine differenzierte Fehlerbehandlung brauchst, musst Du auch die Exceptions auswerten und entspreched reagieren.

    seldon schrieb:

    Es mag Fälle geben, in denen goto eine vertretbare Möglichkeit ist, allerdings sind die damit verbundenen Eigenheiten (beispielsweise in Bezug auf Objektlebenszeiten) derart haarig, dass ich schon etwas Hirnschmalz darauf verwenden würde, es zu vermeiden.

    Wann Objekte sterben ist genau definiert: beim Verlassen des Scopes. IMHO sollte Dich der Compiler darauf aufmerksam machen, wenn Du mit GOTO versuchst, dieses Prinzip auszuhebeln.


  • Mod

    Kleines Experiment mit dem strstr-Teil

    #include <stddef.h>
    typedef unsigned chartype;
    char* strstr (const char* phaystack, const char* pneedle)
    {
     ... Originalcode
    }
    
    char* strstr2(const char* phaystack, const char* pneedle)
    {
        if ( !pneedle[ 0 ] )
            return (char*) phaystack;
    
        if ( !pneedle[ 1 ] )
        {
            for (;;)
            {
                if ( !*phaystack )
                    return 0;
                if ( phaystack[ 0 ] == pneedle[ 0 ] )
                    break;
                ++phaystack;
            }
            return (char*) phaystack;
        }
    
        for (;;)
        {
            for (;;)
            {
                if ( !*phaystack )
                    return 0;
                if ( phaystack[ 0 ] == pneedle[ 0 ] && phaystack[ 1 ] == pneedle[ 1 ] )
                    break;
                ++phaystack;
            }
            {
                ptrdiff_t i = 2;
                for ( ;; )
                {
                    if ( !pneedle[ i + 0 ] )
                        return (char*) phaystack;
                    if ( phaystack[ i + 0 ] != pneedle[ i + 0 ] )
                        break;
                   ++i;
                }
            }
            ++phaystack;
        }
    } 
    
    #include <iostream>
    #include <vector>
    #include <string>
    #include <ctime>
    #include <cstdlib>
    using namespace std;
    
    string randomstring(int length)
    {
        string s;
        for ( ; length--; )
            s.push_back(rand()%256);
        return s;
    }
    
    int main()
    {
        const int x = 10000000;
        srand(time(0));
        string haystack(randomstring(40000000));
        vector<string> needles;
        for ( int i = 0; i < x; ++i )
            needles.push_back(randomstring(rand()%32+1));
        time_t start = clock();
        char a = 0;
        for ( int i = 0; i < x; ++i )
        {
            char* p = strstr(haystack.c_str(),needles[i].c_str());
            a += p ? *p : 0;
        }
        cout << ( clock() - start ) << '\t' << a << '\n';
        start = clock();
        a = 0;
        for ( int i = 0; i < x; ++i )
        {
            char* p = strstr2(haystack.c_str(),needles[i].c_str());
            a += p ? *p : 0;
        }
        cout << ( clock() - start ) << '\t' << a << '\n';
    }
    

    strstr2 benutzt den gleichen Algorithmus, aber ohne den ganzen goto-Unfug. Nicht besonders schön, aber jedenfalls ist eine gewisse Regelmäßigkeit da im Gegensatz zum Ursprungscode.
    Compiliert mit

    g++ -O2 teststrstr.cpp -save-temps -march=core2 -mtune=pentium
    

    mit gcc 4.4.2 für Atom. Da das glibc typischerweise eher mit konservativen Einstellungen gebaut wird, sind diese Flags sinnvoll.
    Ergebnis: strstr2 ist ca 10-15% langsamer als strstr.
    Alle Loops in strstr2 4fach aufgerollt (Code wurde bewusst so geschrieben, dass das mit C&P trivial möglich ist, für den Compiler ist es leider zu kompliziert) - Ergebnis: strstr2 ist ca. 30-40% schneller als strstr. Interessanterweise ist der erzeugte Assemblercode trotzdem kürzer.
    Fazit: Insoweit strstr schneller als andere Implementationen ist, hat das wahrscheinlich nichts mit den gotos zu tun.



  • C++Fan 2010 schrieb:

    Teilweise richtig. Aber wenn Du eine differenzierte Fehlerbehandlung brauchst, musst Du auch die Exceptions auswerten und entspreched reagieren.

    Allerdings muss ich das nicht, wenn kein Fehler auftritt - was, wenn man keinen groben Unfug macht, der häufigere Fall sein dürfte.

    C++Fan 2010 schrieb:

    Wann Objekte sterben ist genau definiert: beim Verlassen des Scopes. IMHO sollte Dich der Compiler darauf aufmerksam machen, wenn Du mit GOTO versuchst, dieses Prinzip auszuhebeln.

    Dummerweise ist deine humble opinion dafür ziemlich unerheblich, und so einfach kommst du auch nicht davon. Nimm etwa

    {
      label:
    
      Komplexer_Typ foo;
    
      for(int i = 0; i < 10; ++i) {
        Komplexer_Typ bar;
    
        if(i == 5) {
          goto label; // Merke: Äußeres Scope wird nicht verlassen.
        }
      }
    }
    

    ...und sag mir, dass du ohne Nachschlagen oder Ausprobieren sofort sagen kannst, was da passiert - zumal dir das Ausprobieren mit einem Compiler nicht beweist, dass das Verhalten tatsächlich wohldefiniert ist. Insbesondere, wenn der Code später geändert werden soll und vor label noch etwas dazu kommt. Oder, um näher beim Threadthema zu bleiben,

    for(int i = 0; i < 10; ++i) {
      if(i == 5) goto label;
    }
    
    // Hier kommt zwanzig Revisionen später neuer Code dazu.
    
    label:
    

    Das sind vermutlich alles lösbare Probleme, aber ich würde einiges wetten, dass die Mehrzahl der Programmierer, die mit so etwas umgehen müssen, sich doch erstmal den Kopf kratzen, wenn sie es sehen.



  • C++Fan 2010 schrieb:

    hustbaer schrieb:

    blah
    blaaaaaaaaaaaaaaaaaaaaaaaah

    Du wolltest mir was mitteilen?

    Ja, nämlich dass es im Fehlerfall nun wirklich nicht auf Performance ankommt.

    Auf das spezielle Beispiel bezogen machen Exceptions natürlich keinen Sinn. Nur das liegt nicht daran dass Exceptions böse wären, oder "zu langsam", sondern einfach daran, dass der Contract von vfs_readdir - offensichtlich - keine Exceptions vorsieht. Im Fehlerfall eine zu werfen, nur um sie dann in der selben Funktion wieder zu fangen, und dann einen Error-Code draus zu machen (damit der Contract eingehalten wird), wäre natürlich plem.

    Also nochmal für die ganz dichten: Exceptions sind nur langsam, wenn man sie auch wirft. Nur dass ein "throw" in einer Funktion vorkommt, in einem Zweig der nicht genommen wird, macht nichts bis fast-nichts (=kein *relevanter* Unterschied).
    Im Gegenteil: die Verwendung von Exceptions kann Programme deutlich schneller machen (so lange Exceptions selten auftreten - wovon ich mal ausgehe), da massenweise Checks ala "if (error) return error;" wegfallen.

    Dein "smarter" Hinweis dass man mit Exceptions "vorsichtig" sein sollte, kombiniert mit einem Link wo beschrieben steht dass Exceptions langsam sein können, ist einfach nur Unfug. Ja, man sollte mit Exceptions vorsichtig sein, genauso wie man mit allem was man schreibt vorsichtig sein soll. Aber sicher nicht weil sie, wenn sie geworfen werden, ein paar Zyklen langsamer sind als ein dummes "return 123;". Denn das ist in der Praxis völlig irrelevant.


  • Mod

    seldon schrieb:

    C++Fan 2010 schrieb:

    Wann Objekte sterben ist genau definiert: beim Verlassen des Scopes. IMHO sollte Dich der Compiler darauf aufmerksam machen, wenn Du mit GOTO versuchst, dieses Prinzip auszuhebeln.

    Dummerweise ist deine humble opinion dafür ziemlich unerheblich, und so einfach kommst du auch nicht davon. Nimm etwa

    {
      label:
    
      Komplexer_Typ foo;
    
      for(int i = 0; i < 10; ++i) {
        Komplexer_Typ bar;
    
        if(i == 5) {
          goto label; // Merke: Äußeres Scope wird nicht verlassen.
        }
      }
    }
    

    ...und sag mir, dass du ohne Nachschlagen oder Ausprobieren sofort sagen kannst, was da passiert - zumal dir das Ausprobieren mit einem Compiler nicht beweist, dass das Verhalten tatsächlich wohldefiniert ist. Insbesondere, wenn der Code später geändert werden soll und vor label noch etwas dazu kommt.

    Falls jemand fragt:
    Der Scope eines automatischen Objektes beginnt mit dessen Definition. Ein Sprung vor diese Definition bedeutet Verlassen des Scopes, somit wird das Objekt zerstört.
    Ein Sprung in den Scope eines automatischen Objektes ist nur zulässig (der Compiler hat sich andernfalls beschweren), wenn es sich um ein POD-Objekt handelt, dass ohne Initialisierer deklariert wurde.


Anmelden zum Antworten