Unterschiedliche Rechenergebnisse: Debug und Release
-
-
Man kann ja dem Compiler beim optimieren ein wenig Sand ins Getriebe streuen, dann gehts. Eine sinnlose bool Zuweisung kann da helfen.
Fürs erste natürlich. Meine Zeit ist im Moment begrenzt.#include <stdio.h> #include "sort.h" #include "value.h" #include "board.h" bool dummy = false; 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 = false; if (value < 0) current->value = value; else if (move_is_capture(move)){ dummy=0; current->value = 50000 + piece_value_midgame(move_piece_captured(move)) - move_piece(move); } 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); }// X64 Release move............e4f3 34581 see value.......0 piece captured..0 piece value.....198 piece...........0 value...........50198 temp............0 is enpassant....1
-
darkfate schrieb:
Tomahawk schrieb:
Sobald ich die Optimierungsflags zurücksetzte (zB O2) gibts keine Unregelmässigkeiten mehr.
Hier ist auch die Lösung des Problems. Die Compilereinstellungen passen nicht zu deinem Code.
Was soll das denn bedeuten? Eine Compileroptimierung darf niemals das beobachtbare Verhalten eines (korrekten) Programms ändern.
Ich vermute mal, dass irgendwo im Programm UB auftritt und dieses erst durch die Optimierungen sichtbar wird.
-
life schrieb:
Was soll das denn bedeuten? Eine Compileroptimierung darf niemals das beobachtbare Verhalten eines (korrekten) Programms ändern.
Stimmt nicht. Wenn ich meinen Linux Kernel kompiliere, dann sind manche Optimierungseinstellungen in der Optimierungsstufe "-O3" sogar tabu.
-
Der Linux Kernel ist auch nicht standard-konform.
-
life schrieb:
Der Linux Kernel ist auch nicht standard-konform.
Man kann nicht alle Probleme Standardkonform lösen.
-
life schrieb:
darkfate schrieb:
Tomahawk schrieb:
Sobald ich die Optimierungsflags zurücksetzte (zB O2) gibts keine Unregelmässigkeiten mehr.
Hier ist auch die Lösung des Problems. Die Compilereinstellungen passen nicht zu deinem Code.
Was soll das denn bedeuten? Eine Compileroptimierung darf niemals das beobachtbare Verhalten eines (korrekten) Programms ändern.
Ich vermute mal, dass irgendwo im Programm UB auftritt und dieses erst durch die Optimierungen sichtbar wird.
UB ?
Diese Programmteile sind genau durchgecheckt. Ausschliessen möchte ich einen Fehler natürlich nicht. Aber in den wenigen Code-Zeilen kann ich nichts entdecken.
Gruss
-
life schrieb:
Eine Compileroptimierung darf niemals das beobachtbare Verhalten eines (korrekten) Programms ändern.
Es gibt zwei Situationen, in denen es der C++-Standard sogar ausdrücklich erlaubt: Bei Rückgabewerten von Funktionen und bei temporären Objekten darf eine unnötige Kopie vom Compiler wegoptimiert werden.
Tomahawk schrieb:
UB ?
Undefined behaviour (undefiniertes Verhalten)
-
life schrieb:
Eine Compileroptimierung darf niemals das beobachtbare Verhalten eines (korrekten) Programms ändern.
Fast alle IDE's compiler verzichten in den Release Versionen auf auf frame pointer, agressives inlining und -O3 egal ob unter Windows oder Linux. Warum wohl?
Wenn es nichts am Programmfluss ändern würde, dann bräuchte man diese Optionen in Release nicht weil sie immer default on wären.
-
Nexus schrieb:
life schrieb:
Eine Compileroptimierung darf niemals das beobachtbare Verhalten eines (korrekten) Programms ändern.
Es gibt zwei Situationen, in denen es der C++-Standard sogar ausdrücklich erlaubt: Bei Rückgabewerten von Funktionen und bei temporären Objekten darf eine unnötige Kopie vom Compiler wegoptimiert werden.
Ja, das stimmt. Aber das sind eben Ausnahmen, die im Standard genau definiert sind. Wenn ein optimierender Compiler machen dürfte, wozu er gerade Lust hat, würden diese Ausnahmen doch wohl kaum im Standard vermerkt sein, oder?

@Tomahawk: Das UB kann ja im Prinzip *irgendwo* auftreten und dadurch in einem ganz anderen Teil des Programms deine Addition zerschießen. Das es daran liegt, ist aber von mir nur geraten. Es könnte natürlich auch tatsächlich ein Compilerbug sein..
-
Ich experimentiere mal weiter, glaube aber kaum, dass ich das Problem verstehen werde und "meinen Frieden finde".
Der Menüpunkt "Inlinefunktionserweiterung" Ob1 oder Ob2 produzieren bei mir das Problem. Mit Ob0 oder Standard arbeitet die Addition korrekt.
Kann man dieses Verhalten interpretieren?
Es ist auch daher interessant, dass die Berechnung korrekt ausgeführt wird, sobald man irgendeine "unsinnige" Codezeile reinschreibt, zB ein print(). Dann klappt es wieder?
Gruss
-
Im MSDN Library habe ich folgende Information zu Option Ob gefunden:
Hinweis
Informationen, die bei Testläufen für die Profilerstellung erfasst wurden, überschreiben Optimierungen, die sonst bei Angabe von /Ob, /Os oder /Ot aktiv sind. Weitere Informationen finden Sie unter Profilgesteuerte Optimierungen (PGO).Da ich im Release meist mit PGO und Testszenarien compiliere, kann ich mir diese Compileroptionen zur Optimierung womöglich sparen?! Übrigens führt das PGO Kompilat - im Gegensatz zum normalen Release - eine korrekte Berechnung durch.
Jetzt such ich nur noch nach dem Beweis, dass mein Code diesen Fehler nicht verursacht.
Gruss
-
Ich würde ja an deiner Stelle mal versuchen den Fehler weiter einzugrenzen. Z.B. behauptest du, dass der Compiler bei
50000 + piece_value_midgame(move_piece_captured(move)) - move_piece(move);die Addition nicht berücksichtigt. Dann lass doch mal
piece_value_midgameeinfach 7 undmove_piece5 zurückgeben. Kommt dann trotzdem 50000 raus? Usw.Edit: So grad mal kompiliert. Assember-output:
; 24 : { ; 25 : temp = 50000 + piece_value_midgame(move_piece_captured(move)) - move_piece(move); test ebx, 32768 ; 00008000H $LN99@sort_c: cmovne edx, esi mov eax, ebx shr eax, 6 and eax, 63 ; 0000003fH movsxd rcx, eax movsxd rax, edx mov r13d, DWORD PTR ?piece_value_mg@?1??piece_value_midgame@@YAHH@Z@4QBHB[r14+rax*4] sub r13d, DWORD PTR ?B@@3Uboard_c@@A[r14+rcx*4+256] add r13d, 50000 ; 0000c350H ; 26 : current->value = temp; mov DWORD PTR [rdi+76], r13dMeinen bescheidenen Assemblerkenntnissen zufolge, führt
addeine Addition durch. D.h. die Addition wurde wohl doch nicht wegoptimiert ;).
-
Alles arbeitet nach unserem Ermessen fehlerfrei. Aber der Microsoft Visual Studio 2010 Professional Compiler mit der Einstellung x64 Release Ob1 oder Ob 2 kompiliert fehlerhaft. Die Ausgabe in der Console muss am Ende heissen:
value........50100 temp.........50100Hier der gesamte und stark vereinfachte Code (kürzer geht's kaum). Am besten rauskopieren und unter Visual Studio 2010 selbst staunen.
#include <stdio.h> typedef unsigned __int64 bitboard_t; // long long const int IS_ENPASSANT = 1 << 15; struct move_info_c { int move; int value; }; struct board_c { int color_on[64]; int piece_on[64]; bitboard_t piece_bb[6]; bitboard_t color_bb[2]; }; class sort_c { public: sort_c(); move_info_c * last_move; move_info_c move_list[256]; }; static bitboard_t pawn_attack_bb[2][64]; static board_c B; static void initialize_fen_position(const char * position, const char * to_move, const char * castle, const char * enpassant); static move_info_c * generate_evasions(move_info_c * move_list); static int static_exchange_evaluation(int move); static void initialize_bitboards(); inline int move_to(int move) { return move & 63; } inline int move_from(int move) { return (move >> 6) & 63; } inline int move_is_enpassant(int move) { return (move & IS_ENPASSANT) != 0; } inline int make_enpassant(int from, int to) { return to | (from << 6) | IS_ENPASSANT; } inline int square_to_file(int square) { return square & 7; } inline int square_to_rank(int square) { return square >> 3; } inline int file_and_rank_to_square(int file, int rank) { return file + (rank << 3); } inline int piece_value_midgame(int piece) { int piece_value_mg[7] = {100, 300, 400, 500, 900, 1000, 0}; return piece_value_mg[piece]; } inline bitboard_t file_and_rank_to_bitboard(int file, int rank) { return static_cast<bitboard_t>(1) << static_cast<bitboard_t>(file_and_rank_to_square(file, rank)); } inline bitboard_t pawn_attack_bitboard(int color, int square) { return pawn_attack_bb[color][square]; } inline int color_on_square(int square) { return B.color_on[square]; } inline int piece_on_square(int square) { return B.piece_on[square]; } inline bitboard_t pawn_bitboard(int color) { return B.piece_bb[0] & B.color_bb[color]; } inline int move_is_capture(int move) { return (piece_on_square(move_to(move)) != 6) || move_is_enpassant(move); } inline int move_piece(int move) { return piece_on_square(move_from(move)); } inline int move_piece_captured(int move) { return move_is_enpassant(move) ? 0 : piece_on_square(move_to(move)); } void initialize_bitboards() { for (int square = 0; square < 64; square++) { int file = square_to_file(square); int rank = square_to_rank(square); pawn_attack_bb[0][square] = 0; pawn_attack_bb[1][square] = 0; if (file - 1 >= 0 && rank + 1 <= 7) pawn_attack_bb[0][square] |= file_and_rank_to_bitboard(file - 1, rank + 1); if (file + 1 <= 7 && rank + 1 <= 7) pawn_attack_bb[0][square] |= file_and_rank_to_bitboard(file + 1, rank + 1); if (file - 1 >= 0 && rank - 1 >= 0) pawn_attack_bb[1][square] |= file_and_rank_to_bitboard(file - 1, rank - 1); if (file + 1 <= 7 && rank - 1 >= 0) pawn_attack_bb[1][square] |= file_and_rank_to_bitboard(file + 1, rank - 1); } } void initialize_fen_position() { for (int square = 0; square < 64; square++) { B.color_on[square] = 2; B.piece_on[square] = 6; } B.color_on[28] = 1; B.piece_on[28] = 0; B.color_on[29] = 0; B.piece_on[29] = 0; } move_info_c * generate_evasions(move_info_c * move_list) { (move_list++)->move = make_enpassant(28, 21); return move_list; } int static_exchange_evaluation(int move) { int from = move_from(move); int to = move_to(move); int me = color_on_square(from); int you = me ^ 1; int piece = piece_on_square(from); int piece_captured = move_piece_captured(move); if ( (pawn_attack_bitboard(me, to) & pawn_bitboard(you)) && (piece_value_midgame(piece) > piece_value_midgame(piece_captured)) && (piece_value_midgame(piece) > 100)) { return piece_value_midgame(piece_captured) - piece_value_midgame(piece); } return 0; } sort_c::sort_c() { int temp = 0; last_move = generate_evasions(move_list); move_info_c * current = move_list; int move = current->move; int value = static_exchange_evaluation(move); if (value < 0) { current->value = value; } else if (move_is_capture(move)) { temp = 50000 + piece_value_midgame(move_piece_captured(move)) - move_piece(move); current->value = temp; } else { current->value = 0; } printf_s("value...........%d\n", current->value); printf_s("temp............%d\n", temp); } int main() { initialize_bitboards(); initialize_fen_position(); sort_c s; return 0; }
-
32 bit funktioniert mit release bei mir
-
also schrieb:
32 bit funktioniert mit release bei mir
Nur mit 64-Bit Release und den optimierenden Flags Ob1 und Ob2 unter Visual Studio 2010 Professional gibts Probleme mit folgender fehlerhaften Consolenausgabe:
value...........50000 temp............50000
-
Bin nicht so fit mit mehrdimensionalen rohen Arrays bei C++, aber könnte es sein, dass es vielleicht
static bitboard_t pawn_attack_bb[64][2];statt
static bitboard_t pawn_attack_bb[2][64];heißen muss?
Edit: Obwohl das ja eigentlich äquivalent sein müsste..
-
life schrieb:
Bin nicht so fit mit mehrdimensionalen rohen Arrays bei C++, aber könnte es sein, dass es vielleicht
static bitboard_t pawn_attack_bb[64][2];statt
static bitboard_t pawn_attack_bb[2][64];heißen muss?
Edit: Obwohl das ja eigentlich äquivalent sein müsste..
Nein, das ist schon korrekt und bedeutet pawn_attack[color][square], also weiss oder schwarz und a1 bis h8.
Habe das Phänomen im CCC Forum gepostet. Dort tummeln sich Schachexperten die auch von Assembler und Compilern Ahnung haben. Bisher geht das Feedback in Richtung Compiler-Bug. Aber einen Beweis gibt es noch nicht.
Wie sieht es denn mit folgendem Code aus?
inline int piece_value_midgame(int piece) { //assert(piece_is_ok(piece)); static const int piece_value_mg[7] = {100, 300, 400, 500, 900, 1000, 0}; return piece_value_mg[piece]; }Ist das sprachlich korrekt eine Look-up-table als static und const in eine Inline Function zu packen? Die Look-up-table wird sonst nirgendwo benötigt und ist in der Function gut aufgehoben (sonst müsste sie extern sein, da die Function normalerweise in einer Header steht). Dadurch kann ich zur Laufzeit den Wert von piece bequem überprüfen und die table kapseln. Allerdings verändert sich das Verhalten des Programms, wenn ich static und const wegnehme oder lasse? Obwohl das doch erstmal egal sein sollte?
Gruss
-
"inline" ist hauptsächlich nur ein Hinweis für den Compiler, dass es lohnenswert sein könnte die Funktion zu inlinen. Wenn sich der Compiler tatsächlich dazu entschließt die Funktion zu inlinen, darf das (beobachtbare) Verhalten dabei nicht verändert werden.
Ansonsten ist mir nur noch das aufgefallen:
return square >> 3Wobei
squareeinintist. Das Ergebnis ist implementation-defined fallssquarenegativ ist.Ich kanns aber leider bei mir nicht testen, da ich kein 64bit System zur Hand habe ;).
-
Im Microsoft Forum wird von einem Compiler-Fehler ausgegangen. Auch im Computerschach Forum spekuliert man darüber. Aber eine konkrete Aussage, von Jemandem, der Ahnung von Compiler-Flags und Assembler hat, war bisher noch nicht zu bekommen.
Bis dahin muss ich hoffen, dass Jemand mit mehr Kenntnissen als ich, eine plausible Erklärung liefern kann
