Unterschiedliche Rechenergebnisse: Debug und Release



  • Alle Konten Daten loggen und beide Files mit Diff anschauen



  • Uninitialisiertes Attribut? Ersetze zum Suchen new durch ein eigenes new, das den Speicher vorher in einen definierten Zustand versetzt.



  • Kannst du das Beispiel nicht auf weniger Knoten drücken? - Dann käme wohl mal ein Debugger und du alles mitrechnest.



  • volkard schrieb:

    Uninitialisiertes Attribut? Ersetze zum Suchen new durch ein eigenes new, das den Speicher vorher in einen definierten Zustand versetzt.

    Das wären 3 Hashtabellen, die auf dem Heap liegen und weitere Speicherbereiche auf dem Stack.

    Erkenntnisse folgen (hoffentlich) in Kürze...

    Gruss
    T.



  • Tomahawk schrieb:

    loks schrieb:

    In 9 von 10 Fällen: Nicht initialisierte Variablen....

    Hätte der BoundsChecker sofort gefunden, schätze ich mal.

    Du sollst nicht "schätzen". Entweder Du weist es, oder Du weist es nicht und überprüfst es.

    Also teste ob der Boundschecker folgendes bemängelt oder nicht:

    int i;
    
    int j = i + 5;
    

    Wenn das angewarnt (und von Dir dann auch in Folge beseitigt wird) ok. Andernfalls bleib ich bei meinen nicht initialisierten Variablen 😉



  • Wenn du im Release Modus Optimierungen durchführst und irgendwo eine parallele Schleife entsteht, dann musst du zusehen dass deine Algorithmen Thread Safe sind.

    Das Problem liegt beim richtigen Code an den Fließpunktzahlen die nicht wie Reele Zahlen in sich ein geschlossenes System bilden.

    Vor allem langen Schleifen und unterschiedlichgroßen Zahlen tritt das Problem sehr häufig und gravierend auf. Lies dich hier durch:

    http://software.intel.com/en-us/articles/parallelization-and-floating-point-numbers/

    Generell hat der Autor sehr viele Tips wie man solche Pitfalls vermeiden kann:
    http://software.intel.com/en-us/profile/336629/contributions

    Schau den optimierten Code(IDE Abhängig) bevor er kompiliert wird.
    Lass dir alle Zwischenergebnisse inklusive der Iterationsnummer in eine Datei ausgeben, dann siehst du wo und ab wann der Fehler auftritt.



  • Wer in der Schach-Engine Fließkommazahlen benutzt, gehört auf die Couch.



  • Ne Schach-KI mit Fließkommazahlen, dass bringt endlich mal die richtige dynamik ins Spiel.



  • Hab zu schnell über den Text gelesen... nebenbei noch die WM..

    Im übrigen gilt das Thread Safe Problem auch für Integer, je nach Optimierung kann es da auch zu Anomalien kommen...



  • volkard schrieb:

    Wer in der Schach-Engine Fließkommazahlen benutzt, gehört auf die Couch.

    y not?

    Ich hab zwar keine Ahnung von Stellungsbewertungen...
    Aber wie macht man denn sonst ne fuzzy Stellungsbewertung?

    Oder macht man sie nicht fuzzy?



  • y not? schrieb:

    Aber wie macht man denn sonst ne fuzzy Stellungsbewertung?
    Oder macht man sie nicht fuzzy?

    Festkommazahlen würde ich vielleicht nehmen. Also blanke integers, die nur zur Bildschirmausgabe durch was festes (hier 2^23) geteilt werden.
    Nehmen wir mal einen reinen Materialzähler nach http://de.wikipedia.org/wiki/Schachfigur#Tauschwert
    und 3+3+3+3+5+5+9+ 8*9 (8 zur Dame umgewandelte Bauern)=103, für den König nehme ich dann mal 127 an, weil den zu verlieren ist schlimmer als alles andere, dann brauche ich 8 Bit und eins für das Vorzeichen und kann bei einem int32 noch 23 Nachkommabits nehmen.



  • Habe die Stelle im Code gefunden, wo es einen Unterschied zwischen den Kompilaten gibt.

    x32 Release
    x32 Debug
    x64 Debug
    x64 Release PGO

    Alle Kompilate führen zu identischen Ergebnissen. Nur das x64 Release hat Abweichungen. In folgendem Code wird eine Addition nicht ausgeführt:

    void sort_c::score_evasions(bool print) {
      if (last_move - move_list <= 1) return;
    
      for (move_info_c * current = move_list; current != last_move; current++) {
        int move = current->move;
        int value = static_exchange_evaluation(move);
    
        if (value < 0) {
          current->value = value;
        }
        else if (move_is_capture(move)) {
          current->value =   piece_value_midgame(move_piece_captured(move))  // Addition wird nicht ausgeführt
                           - move_piece(move)
                           + HISTORY_MAX;  // 50000
        }
        else {
          current->value = history_move_ordering_score(move);
        }
    
        if (print) {
          if (move_from(move) == SQUARE_E4) {
            char string[256];
            printf_s("value = %d\n", current->value);
            fflush(stdout);
          }
        }
      }
    }
    

    current->value muss sich wie folgt berechnen:
    100 - 0 + 50000 = 50100

    Der Compiler erzeugt aber nur 50000. Habe alle Daten genau überprüft und kann nichts finden. Meine einzige Erklärung, dass der 64-Bit Compiler von MS Visual Studio 2010 Professional fehlerhaft compiliert?! Verstehe aber nichts von Assembler, daher bringt es mich nicht weiter mit die Assemblerausgaben anzusehen.

    Probiere jetzt den 2008er Compiler aus.

    Ich verstehe es nicht.

    Gruss
    T.



  • Sobald ich die Compilereinstellungen für Release x64 ändere addiert der Compiler wieder korrekt?!



  • Tomahawk schrieb:

    Sobald ich die Compilereinstellungen für Release x64 ändere addiert der Compiler wieder korrekt?!

    Der Code den du gepostet hast ist so nicht ausführbar. Wieso debuggst du ihn nicht einfach? Du könntest auch mal die Startwerte für einzelnen Variablen durchgeben.

    1. Geh Iteration für Iteration durch und schau ab wann der Fehler auftaucht. Wenn es viele Ergebnisse sind dann gebe zwei Dateien aus und dann siehst du ab welcher Iteration der Wert unterschiedlich ist.

    2. Poste mal die Compiler Flags.

    3. Wenn du irgendwelche Optimierungen im Release Modus nutzt, dann schalte sie Schritt für Schritt aus.

    4. Ich habe auch die VS2010 und diese hat einen sehr angenehmen Debug Modus.



  • darkfate schrieb:

    Tomahawk schrieb:

    Sobald ich die Compilereinstellungen für Release x64 ändere addiert der Compiler wieder korrekt?!

    Der Code den du gepostet hast ist so nicht ausführbar. Wieso debuggst du ihn nicht einfach? Du könntest auch mal die Startwerte für einzelnen Variablen durchgeben.

    1. Geh Iteration für Iteration durch und schau ab wann der Fehler auftaucht. Wenn es viele Ergebnisse sind dann gebe zwei Dateien aus und dann siehst du ab welcher Iteration der Wert unterschiedlich ist.

    2. Poste mal die Compiler Flags.

    3. Wenn du irgendwelche Optimierungen im Release Modus nutzt, dann schalte sie Schritt für Schritt aus.

    4. Ich habe auch die VS2010 und diese hat einen sehr angenehmen Debug Modus.

    Leider bin ich im Lesen von Assemblercode nicht so fit. Daher kann ich die "fehlerhafte" Release Engine nicht debuggen. Die Debugversion habe ich schon gründlich untersucht.

    Hier die Compilerflags:

    /nologo /W4 /WX- /O2 /Ob2 /Oi /Ot /Oy /GT /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /GF- /Gm- /MT /GS- /Gy /arch:SSE2 /fp:fast /Zc:wchar_t /Zc:forScope /GR- /Fp"x64\Release\Loop 2010.pch" /Fa"x64\Release\" /Fo"x64\Release\" /Fd"x64\Release\vc100.pdb" /Gr /errorReport:queue /favor:INTEL64

    Sobald ich die Optimierungsflags zurücksetzte (zB O2) gibts keine Unregelmässigkeiten mehr.

    Gruss
    T.



  • Mit dem MS 2008 Compiler gibts keine Unregelmässigkeiten.

    Bisher ist es tatsächlich nur das 64-Bit Release Build des MS 2010 Compilers mit obigen Flags.

    Auch verschiedene Versionen des BoundsChecker konnten nach mehrtägigen Analysen keine Probleme feststellen.

    Gruss



  • Tomahawk schrieb:

    Leider bin ich im Lesen von Assemblercode nicht so fit.

    Wenn Assembler überhaupt nicht geht, dann hilft nur die gute alte Fileausgabe, oder cout, so dass du dir den Status aller Variablen in der Console oder irgend einer Textbox ausgeben lässt. Datei ist besser, weil du siehst welcher Thread wann zuerst schreibt oder priorisiert wird.

    Alternativ lässt sich der optimierte Code, der vor dem kompilieren erstellt wurde auch behalten ohne ihn nach dem optimieren zu löschen. Aber dieser ist nach getätigten Optimierungen manchmal fast genau so aufschlussreich wie Assemblercode

    Oder du gibst, falls es möglich ist, die Startwerte der Variablen.

    Hier die Compilerflags:

    Tomahawk schrieb:

    /nologo /W4 /WX- /O2 /Ob2 /Oi /Ot /Oy /GT /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /GF- /Gm- /MT /GS- /Gy /arch:SSE2 /fp:fast /Zc:wchar_t /Zc:forScope /GR- /Fp"x64\Release\Loop 2010.pch" /Fa"x64\Release\" /Fo"x64\Release\" /Fd"x64\Release\vc100.pdb" /Gr /errorReport:queue /favor:INTEL64

    Hui, du hast alles angeschaltet was sich nach irgendwie Geschwindigkeit angehört hat. Du solltest hingehen und den Profiler nutzen. Manchmal haben die Einstellungen auch keinen Mehrwert und bringen nur aufgeblähten Code mit sich.

    Nebenbei: Man sollte nur den Rechenteil so intensiv optimieren. Besser: Mit schnelleren Algorithmen als mit agressiven Compiler Flags. GUI oder ähnliches nur mit Standard Release Einstellungen optimieren.

    Wahrscheinlich ist auch dass alle Release Versionen irgendwo ein Problem haben, nur zeigt es sich an einer Stelle, die du zum Glück beobachten kannst.

    Zuerst: Schalte mal nur /MP ab und versuche es noch einmal mit den selben Einstellungen. Wenn sich die beiden beißen, dann muss man die for Schleife ein wenig überdenken.

    Nächster Vorschlag. Du stellst überall die Release Optimierungs-Einstellungen auf Default.

    Wenn du SSE2 Nutzt, dann solltest du das Alignment auf 128Bit setzen:
    Lies dir bei Gelegenheit das mal durch: http://msdn.microsoft.com/en-us/library/aa290049(VS.71).aspx
    Compiler Flag /Zp16

    Flag /GT nur wenn du Fibers oder TLS nutzt.

    Tomahawk schrieb:

    Sobald ich die Optimierungsflags zurücksetzte (zB O2) gibts keine Unregelmässigkeiten mehr.

    Riecht nach einer Überoptimierung.



  • Bin ratlos und meine Kollegen bei den Softwarentwicklern haben auch keine Idee mehr. Vielleicht liegt es an den Kompilereinstellunge, aber darüber kann ich keine Aussage machen.

    Warum werde im x64 Release Modus 2 int Zahlen nicht addiert.

    Gibt es die Möglichkeit den Quellcode hier zu veröffentlichen (~1500 Zeilen) und mal Jemanden mit Ahnung von Assembler und Compiler Settings drüberschauen zu lassen?

    Vollkommen ratlos und erschöpft... 😞



  • Hier mal den Code:

    class sort_c {
    public:
      sort_c();
    
    private:
      move_info_c * last_move;
      move_info_c move_list[256];
    };
    
    sort_c::sort_c() {
      char string[256];
      int temp;
    
      last_move = generate_evasions(move_list);
    
      move_info_c * current = move_list+8;
    
      int move = current->move;
      int value = static_exchange_evaluation(move);
    
      temp = 0;
    
      if (value < 0) {
        current->value = value;
      }
      else if (move_is_capture(move)) {
        // hier wird nicht addiert
        temp = 50000 + piece_value_midgame(move_piece_captured(move)) - move_piece(move);
        current->value = temp;
      }
      else {
        current->value = 0;
      }
    
      printf_s("move............%s %d\n", move_to_string(move, string), move);
      printf_s("see value.......%d\n", value);
      printf_s("piece captured..%d\n", move_piece_captured(move));
      printf_s("piece value.....%d\n", piece_value_midgame(move_piece_captured(move)));
      printf_s("piece...........%d\n", move_piece(move));
      printf_s("value...........%d\n", current->value);
      printf_s("temp............%d\n", temp);
      printf_s("is enpassant....%d\n", move_is_enpassant(move));
      printf_s("\n");
      fflush(stdout);
    }
    

    Es werden in einer Stellung 9 Züge generiert. Danach untersuche ich den betreffenden Zug Nr 9, also move_list+8. Die print-Ausgaben stimmen zwar alle, aber temp und current->value ergeben nur 50000 statt 50198 (wie es sein sollte und von allen gtesteten Compilern gemacht wird).



  • Ich würde meine eMail Adresse hier angeben, wer möchte kann mich kurz anschreiben und ich sende den Code zu.

    Gruss
    T.


Anmelden zum Antworten