Makros oder Funktionen?



  • In meinem (etwas älteren) C++ Buch wird behauptet, dass man mit Makros eine Beschleunigung der Programmausführung erreichen kann, wohingegen das Anspringen von Funktionen zeitaufwendig ist. Ich frage mich, ob das nur für C-Programmierer gilt, oder ob das auch für die C++ Programmierung gültig ist? Angeblich, so habe ich gelesen, hat man mit einer inline-Funktion ähnliche Vorteile wie mit einem Makro, aber leider kann man nie vorhersehen, ob der Compiler eine mit 'inline' deklarierte Funktion auch wirklich akzeptiert, weil es nur eine Empfehlung an den Compiler darstellt, an die er sich nicht zu halten braucht.

    Wie sieht das in diesem Fall aus:

    #define GET_LONG_BYTE2(x) (((x) >> 16L) & 127L)
    

    oder:

    inline long GET_LONG_BYTE2(long x){
    return ((x) >> 16L) & 127L;
    }
    

    Es soll der Wert des zweiten Bytes einer long_Variablen ermittelt werden.
    Habe ich hier eine Chance, dass der Compiler das als inline akzeptiert? In welchen Fällen kann ich denn davon ausgehen, dass eine inline-Funktion auch wirklich als inline akzeptiert wird? Oder ist es vernünftiger, in den Fällen, wo man nicht sicher ist, prinzipiell ein Makro zu verwenden?

    Danke für Ratschläge.





  • Imhotep3 schrieb:

    In meinem (etwas älteren) C++ Buch wird behauptet, dass man mit Makros eine Beschleunigung der Programmausführung erreichen kann, wohingegen das Anspringen von Funktionen zeitaufwendig ist...

    Kann gut sein ... aber es hat sich inzwischen herausgestellt, dass die anderen Aspekte (Nachteile von Makros/Vorteile von Funktionen) in der Praxis bei weitem überwiegen.

    ... und auch Makros kann man so einsetzen, dass sie auch keinen Performancevorteil mehr bieten - oder sogar noch langsamer sind.

    Das "Performanceargument" ist typisch für die ersten Tage von C (und auch noch in die erste C++-Ära hinein) ... hat sich aber inzwischen weitestgehend überlebt.

    Gruß,

    Simon2.



  • Imhotep3 schrieb:

    Oder ist es vernünftiger, in den Fällen, wo man nicht sicher ist, prinzipiell ein Makro zu verwenden?

    In C++ haben (solche) Makros einfach nichts mehr verloren. Punkt.

    Lange Erklärung: http://www.research.att.com/~bs/bs_faq2.html#macro



  • Imhotep3 schrieb:

    ... Oder ist es vernünftiger, in den Fällen, wo man nicht sicher ist, prinzipiell ein Makro zu verwenden?

    ...

    Richtig, Makros können "inline" erzwingen. - (Der Programmierer hat den Hut auf, nicht der Compiler!).

    Simon2 schrieb:

    Das "Performanceargument" ist typisch für die ersten Tage von C (und auch noch in die erste C++-Ära hinein) ... hat sich aber inzwischen weitestgehend überlebt.

    Gruß,

    Simon2.

    Makros sind und bleiben Teil von C++ !

    MfG



  • freak11 schrieb:

    ...

    Simon2 schrieb:

    Das "Performanceargument" ist typisch für die ersten Tage von C (und auch noch in die erste C++-Ära hinein) ... hat sich aber inzwischen weitestgehend überlebt.

    Gruß,

    Simon2.

    Makros sind und bleiben Teil von C++ !

    MfG

    😕
    Was hat das mit meinem Text zu tun ?

    Ich habe nie bestritten, dass Makros Teil von C++ sind - und auch nicht, dass ihr Einsatz an bestimmten Stellen hilfreich (und sei es nur als "kleinstes Übel" 😉 ) ist.
    Nur die starke Betonung von "Performance" bei allem Möglichen (u.a. Makros - aber auch an vielen anderen stellen) - wird mMn den aktuellen Erfahrungen in der Entwicklung nicht mehr gerecht.

    Gruß,

    Simon2.



  • freak11 schrieb:

    Imhotep3 schrieb:

    ... Oder ist es vernünftiger, in den Fällen, wo man nicht sicher ist, prinzipiell ein Makro zu verwenden?...

    Richtig, Makros können "inline" erzwingen. - (Der Programmierer hat den Hut auf, nicht der Compiler!).

    ....
    Makros sind und bleiben Teil von C++ !

    Makros sind und bleiben wegen ihrer vielen Nachteile etwas das man nur verwenden sollte wenn es nötig ist (Includeguards, Plattform"switches" z.b.) und ansonsten sollten sie lieber vermieden werden!

    Davon abgesehen sind Makros zwar ein Bestandteil des C++ Präprozessors, im Gegensatz zu Templates etc. aber kein Bestandteil zur Compilezeit (was u.a. die Fehlersuche extrem erschwert).

    cu André



  • Makros sind Bestandteil von C++ - ja. Das ist aber noch lange kein Argument für die Nutzung von Makros. Oder benutzt du auch Trigraphsequenzen? Denn die sind genauso Bestandteil von C++. Es gibt nunmal Dinge die man normalerweise garnicht oder nur mit sehr viel Vorsicht und Überlegung nutzen sollte. Makros als Funktionsersatz, Konstanten-ersatz usw. fallen da eher unter die Kategorie "normalerweise garnicht".
    Wo aber Makros z.B. nützlich sein können, ist, um das ständige Wiederholen von ähnlichen Texten zu vermeiden, aber auch nur dort wo das mit den eigentlichen Sprachmitteln von C++ nicht möglich ist.
    Ein Beispiel das ich vor ner Weile eingebaut hab:

    /edit:

    #define MAKE_BINARY_EXPRESSION(name,sign) \
        template<typename LeftInner, typename RightInner> \
          struct name##_expression \
          : public basic_expression { \
            typedef CanExist<expression_traits<LeftInner>::is_expression> LeftInnerOK; \
            typedef CanExist<expression_traits<RightInner>::is_expression> RightInnerOK; \
            typedef typename LeftInner::result_type LeftResult; \
            typedef typename RightInner::result_type RightResult; \
            typedef typename name##_traits<LeftResult,RightResult>::result_type result_type; \
            name##_expression(LeftInner const& leftinner, RightInner const& rightinner) \
              : leftinner_(leftinner), rightinner_(rightinner) {} \
            result_type operator() () const { \
              LeftResult lres = leftinner_(); \
              RightResult rres = rightinner_(); \
              result_type result = (lres sign rres); \
              return result; \
            } \
          private: \
            LeftInner leftinner_; \
            RightInner rightinner_; \
          }; \
          \
        template<class LE, class RE> \
          name##_expression<LE,RE> operator sign(LE const& lexpr, RE const& rexpr) {\
            STATIC_CHECK(expression_traits<LE>::is_expression || \
                         expression_traits<RE>::is_expression , \
                         binary_expression_op_without_expression_arguments); \
            name##_expression<LE,RE> result(lexpr,rexpr); \
            return result; \
          }
    
        MAKE_BINARY_EXPRESSION(add,+);
        MAKE_BINARY_EXPRESSION(diff,-);
        MAKE_BINARY_EXPRESSION(mult,*);
        MAKE_BINARY_EXPRESSION(div,/);
        MAKE_BINARY_EXPRESSION(mod,%);
    
        MAKE_BINARY_EXPRESSION(left_shift,<<);
        MAKE_BINARY_EXPRESSION(right_shift,>>);
    
        MAKE_BINARY_EXPRESSION(bitand,&);
        MAKE_BINARY_EXPRESSION(bitor,|);
        MAKE_BINARY_EXPRESSION(bitxor,^);
    
        MAKE_BINARY_EXPRESSION(less,<);
        MAKE_BINARY_EXPRESSION(leq,<=);
        MAKE_BINARY_EXPRESSION(greater,>);
        MAKE_BINARY_EXPRESSION(geq,>=);
    
        MAKE_BINARY_EXPRESSION(eq,==);
        MAKE_BINARY_EXPRESSION(neq,!=);
    
        MAKE_BINARY_EXPRESSION(and,&&);
        MAKE_BINARY_EXPRESSION(or,||);
    
    #undef MAKE_BINARY_EXPRESSION
    

    Ich denke es ist klar, dass man den oberen Code nicht 18 mal schreiben will mit jeweils etwas anderen Namen und Operatoren...
    Wichtig ist bei sowas aber auf jeden Fall das undef am Ende, da das Marko nicht mehr gebraucht wird.



  • So, jetzt ist mir mein Kennwort wieder eingefallen, ich war ja schon ewig lang nicht mehr hier:) Thx für die Tipps.

    Soweit ich mich vage erinnern kann, werden nur 'winzige' Funktionen auch tatsächlich als inline akzeptiert, nur ist es nicht so klar, wo das 'winzig' anfängt und wo es aufhört. Für die kleine Minifunktion oben nehme ich einmal an, dass sie 'winzig' genug ist, um als 'inline' durchzugehen.

    Wie schon erwähnt, schreibe ich zur Zeit an einer Schach-Engine, und bei der Suchfunktion zählt jede Millisekunde an Performance -> man könnte auch sagen, Performance ist bei so meinem Projekt mindestens genau so wichtig wie sauberes Design (50 - 50), im Unterschied zu den meisten anderen Projekten, wo Design weitaus wichtiger als Performance ist.
    Braucht der Zuggenerator um 1 Takt länger, als es sonst mit optimiertem Code auch möglich wäre, rechnet sich das in Anbetracht dessen, dass er millionenfach aufgerufen werden muss, sehr schnell hoch, und schon kann man auf den Computerzug einige Sekunden länger warten, als es notwendig gewesen wäre.
    Eine gewisse Mindestsuchtiefe soll schließlich auch dann noch erreichbar sein, wenn das Programm in Zeitnot gerät.

    Deshalb versuch' ich zur Zeit möglichst diese Performance-Feinheiten zu berücksichtigen, angeblich soll es möglich sein, jeden rekursiven Algorithmus auch in Schleifenform statt durch rekursive Funktionsaufrufe zu realisieren.
    Die Schleifenvariante soll dann angeblich um 40% schneller sein. Zumindest konnte ich das so im Netz recherchieren. -> Das Vorhaben, den Alpha_Beta Algorithmus in reine Schleifenform zu überführen, gestaltet sich leider ziemlich kompliziert.

    Ich hatte noch nie sonderliches Talent für gutes Design, hier mal der Header von meiner alten Engine (die leider nur 5 Halbzuege in 8 Sekunden Rechenzeit zustande brachte -> deshalb alles noch mal von vorne und mit anderem Design -hoffentlich wird es diesmal besser).
    Vielleicht kann ja jemand einige grundlegende Designfehler entdecken, an denen die Langsamkeit der alten Engine auch liegen könnte, mir fehlt dazu einfach das fachmännische Auge, weil ich nur zum Hobby programmiere.

    #ifndef MYCHESS_CONSTANTS
    #define MYCHESS_CONSTANTS
    
    #include <stdlib.h>
    #include <string.h>
    
    #include <stdio.h>
    
    /* Contains the Fenstring of the starting position */
    char* StartPosition = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
    
    /* indicates that a char-variable of this type stores a square's coordinates,
       (internal format) */
    typedef char iSquare;
    
    /* internal board representation */
    #define NUMBER_OF_FILES 8
    #define NUMBER_OF_RANKS 8
    
    #define KING_ID 1
    #define QUEEN_ID 2
    #define ROOK_ID 3
    #define BISHOP_ID 4
    #define KNIGHT_ID 5
    #define PAWN_ID 6
    #define PIECE_MASK 7
    #define GET_PIECE_TYPE(x) ((x) & PIECE_MASK)
    
    #define NUMBER_OF_DIFFERENT_PIECES 6
    
    #define WHITE 16
    #define BLACK 32
    #define COLOR_MASK 48
    #define GET_PIECE_COLOR(x) ((x) & COLOR_MASK)
    
    #define WKCASTLE 1
    #define WQCASTLE 2
    #define BKCASTLE 4
    #define BQCASTLE 8
    #define ANY_WCASTLE 3
    #define ANY_BCASTLE 12
    
    /* this is used to determine castling moves */
    #define INITIAL_WHITE_KING_FILE 4
    #define INITIAL_WHITE_KING_RANK 0
    #define INITIAL_BLACK_KING_FILE 4
    #define INITIAL_BLACK_KING_RANK 7
    
    #define MAXIMUM_NUMBER_OF_PIECES 16
    
    struct situation{
    	char square[NUMBER_OF_FILES][NUMBER_OF_RANKS];
    	char extraInfo; /* stores side to move and castling availabilties */
    	iSquare epSquare;
    	iSquare white_Pieces[MAXIMUM_NUMBER_OF_PIECES];
    	iSquare black_Pieces[MAXIMUM_NUMBER_OF_PIECES];
    
    	char king_w, king_b;
    	/* very important to check for allowed moves */
    	char hMoves;
    };
    
    /* (1) Allocates Memory for situation* CurrentBoard */
    int init();
    
    /* setup of the internal Board based on the information given by a FENSTRING */
    int create_internal_board(char* fenString);
    
    /* converts a square that is given by algebraic notation into internal repres. */
    iSquare alg_convert(char aFile, char aRank);
    
    /* converts a square that is given by two indizes into internal repres. */
    iSquare index_convert(int iFile, int iRank);
    
    /* converts an iSquare to algebraic characters */
    void convert_to_alg(iSquare src, char* destFile, char* destRank);
    
    /* the buffer must be at least 3 bytes of size */
    void convert_to_string(iSquare src, char* buffer);
    
    /* buck_sort */
    void buck_sort(short table_index);
    
    #define NUMBER_OF_DIRECTIONS 9
    
    struct Destination{
    	char number_of;
    	iSquare* dest_Square;
    };
    
    /*
    #define GET_FILE(x) ((x) >> 4)
    #define GET_RANK(x) ((x) & 7)
    */
    #define INVALID_SQUARE 127
    #define STANDARD_DIR 0
    #define SOUTHWEST 1
    #define WEST 2
    #define NORTHWEST 3
    #define NORTH 4
    #define NORTHEAST 5
    #define EAST 6
    #define SOUTHEAST 7
    #define SOUTH 8
    
    inline char GET_FILE(iSquare src);
    
    inline char GET_RANK(iSquare src);
    
    #define NUMBER_OF_CRITERIA 7
    
    /* a helper function which initializes the (crazy) fixed_Destination array,
       this is called by init() and shouldn't be called at any other occasion */
    int create_Destinations(int piece_type, int file, int rank);
    
    iSquare add_vector(iSquare start_point, int vec_x, int vec_y);
    
    struct chess_vector{
    	int vec_x;
    	int vec_y;
    };
    
    /*
    short get_direction(iSquare start, iSquare target);
    */
    
    chess_vector subtract(iSquare top, iSquare base);
    
    void multiply_vector(chess_vector* source, chess_vector* dest, int scalar);
    
    int is_Corner_Square(int file, int rank);
    
    int en_passant_extra(situation* tested, iSquare active);
    
    #define NO_CAPTURE 16
    
    /* extra: for the case of a pawn promotion, also a capturing flag */
    struct move{
    	iSquare start;
    	iSquare dest;
    	char active_id;
    	char captured_id;
    	char promote;
    	short quality;
    };
    
    #define MOVE_TABLE_DIMENSION 100
    
    struct move_table{
    	unsigned short number_of;
    	char mate;
    	char check;
    	move stored_move[MOVE_TABLE_DIMENSION];
    };
    
    int init_move_tables();
    
    #define CHECK_COUNTER_MASK 15L
    #define CHECK_SOURCE_MASK 255L
    
    #define CHECK_DIR_MASK 240
    #define GET_CHECK_DIRECTION(x) (((x) & CHECK_DIR_MASK) >> 4)
    
    /* these may only be called after the necessary initializing is done */
    long is_under_check(situation* examined, iSquare xy);
    
    /* the lock_indicator must be an array of 9 shorts */
    int lock_directions(situation* examined, iSquare piece_loc,
    					 short* lock_indicator);
    
    int find_moves(situation* examined, move_table* results);
    
    int find_quickly(situation* examined, move_table* results);
    
    int quick_check(situation* examined, iSquare xy);
    
    void move_to_alg(move* src, char* destString);
    
    void alg_to_move(char* src, move* destMove);
    
    int get_Move_index(move* question, move_table* setOfMoves);
    
    char execute_move(situation* board, move* xy);
    
    #include <math.h>
    
    /* these are used by the evaluation function */
    
    #define MAX_DEPTH 5
    
    #define PAWN_VALUE 100
    #define KNIGHT_VALUE 300
    #define BISHOP_VALUE 300
    #define LIGHT_PIECE_VALUE 300
    #define ROOK_VALUE 450
    #define QUEEN_VALUE 900
    #define KING_VALUE 15000
    
    #define DEVELOPPED_PIECE 17
    #define GOOD_KNIGHT 15
    #define BAD_KNIGHT 10
    #define NO_CASTLE 21
    #define CASTLING_LOST 43
    #define CENTER_PAWN 27
    #define HALF_CENTER 13
    #define OPEN_FILE 15
    #define OPEN_RANK 15
    #define QUEEN_MATE_FACTOR 10
    #define QUEEN_MATE_FACTOR_2 4
    #define SIDE_TO_MOVE_BONUS 27
    #define DANGEROUS_FILE 18
    #define DANGEROUS_RANK 18
    #define PASSED_PAWN 39
    #define PASSED_PAWN_BONUS 8
    #define DANG_BISHOP 7
    #define MULTIPLE_PAWNS 3
    #define ISOLATED_PAWN 1
    #define QUEEN_TOO_SOON 6
    #define ROOK_MATE_FACTOR 20
    #define ROOK_MATE_FACTOR_2 10
    
    #define DANGEROUS_QUEEN 5
    #define ACTIVE_KING_ENDGAME 50
    #define UNDEVELOPPED_PIECE 28
    #define CENTER 20
    #define ROOK_FILE_BONUS 3
    #define DEVELOPPED_BISHOP 15
    
    void undo_move(situation* pre_sit, situation* undo, move* xy);
    
    /* this is used by the evaluation function  */
    struct ev_Table{
    	char pawn_count_w[NUMBER_OF_FILES];
    	char pawn_count_b[NUMBER_OF_FILES];
    	char pawn_count_w_rank_id[NUMBER_OF_FILES];
    	char pawn_count_b_rank_id[NUMBER_OF_FILES];
    
    	char pawn_count_w_r[NUMBER_OF_RANKS];
    	char pawn_count_b_r[NUMBER_OF_RANKS];
    	char rook_count_b[NUMBER_OF_FILES];
    	char rook_count_w[NUMBER_OF_FILES];
    	char rook_count_b_r[NUMBER_OF_RANKS];
    	char rook_count_w_r[NUMBER_OF_RANKS];
    
    	char piece_count_w, piece_count_b;
    	iSquare w_queen, b_queen;
    	iSquare w_king, b_king;
    	iSquare w_bishop[2];
    	iSquare b_bishop[2];
    	char w_bishop_count, b_bishop_count;
    	char w_queen_count, b_queen_count;
    	char w_rook, b_rook;
    	char w_rook_count, b_rook_count;
    };
    
    #define STALE_MATE 0
    
    int evaluate_moves(int your_depth, situation* pos_searched);
    
    /* the very central, recursive alpha_beta function */
    short THINK(situation* pos, int your_depth);
    
    #define RANDOM_WINDOW 7
    
    void __cdecl uci_extra(void* threadData);
    
    /* this is called in response to the UCI-command 'go' */
    int search_best_move(char* result);
    
    #endif
    
    #include <stdlib.h>
    #include <process.h>
    
    /*	in order to measure performance with QueryPerformanceCounter() */
    #include <windows.h>
    


  • naja erstmal solltest du auf aktuelle C++-Header umsteigen. die <stdlib.h> und Konsorten sind seit 10 Jahren veraltet, <cstdlib> ist der entsprechende header aus dem Standard.

    Des weiteren sind die ganzen Makros die du für deine Konstanten verwendet hast ein Musterbeispiel dafür, wie man Makros nicht verwenden sollte. Mach statt dessen ordentliche Deklarationen von const ints oder const short oder auch const unsigned char draus. Genauere Begründungen wiederhole ich jetzt nicht, die kann man an zig stellen nachlesen, z.B. in Scott Meyers' Effective C++

    Des weiteren ist das was du da geschrieben hast ein auf den ersten Blick recht unübersichtliches Gewirr von einzelnen Funktionen und von structs die nur als Datenpakete benutzt werden. Es gibt diverse Dinge in C++ die du nicht benutzt (Klassen, Kapselung usw. usw.)

    Ich unterstelle mal dass du von C her kommst oder über C angefangen hast, C++ zu lernen (ein Ansatz den leider viele veraltete und einige schlechte neue Bücher gehn) und noch nicht sehr weit gekommen bist. (Ist nicht böse gemeint)

    Ich denke mal, bevor du dir Gedanken um die Performance machst, gibts einige Designtechnische Geschichten die du dir aneignen kannst, die mühelos sehr viel mehr Performance bringen als der Unterschied zwischen Makros und inline-Funktionen. Und wenn man schon dabei ist, dann gibts noch einen viel größeren Faktor der die Performance beeinflusst: die zugrundeliegenden Algorithmen. Wo ein kleiner Kniff im Code wenige Prozessortakte gutmachen kann, kann ein falsch gewählter Algorithmus die Laufzeiten quadratisch oder sogar exponentiell explodieren lassen. Da sind solche kleinen Eingriffe vergeben Liebesmüh.

    Die bessere Vorgehensweise wäre also folgende:

    1. einen schnellen, effizienten Algorithmus finden
    2. das Problem mit sauberem, übersichtlichen und wartbarem Design lösen

    Wenn danach Performanceprobleme bestehen, sollte man erst einen Profiler befragen, wo man was ändern muss, und dann die Änderungen vornehmen, die nötig sind (immer unter Aufsicht des Profilers).


Anmelden zum Antworten