Can you confirm that this design do not create an overhead ?

Programming Topics (Computer Chess) and technical aspects as test techniques, book building, program tuning etc

Moderator: Andres Valverde

Can you confirm that this design do not create an overhead ?

Postby mathmoi » 05 Jan 2006, 19:03

Hi, I want to push OO to it's limit in order to get cleaner code. Here is what I want to do :

Code: Select all
class CSquare
   {
   private:
      unsigned int m_uiSquareIndex;
   public :
      // the next 3 functions allow CSquare to be used as an unsigned int in
      // arithmetic operations.
      inline CSquare(unsigned int);
      inline CSquare operator=(unsigned int);
      inline operator unsigned int();

      // The next 2 functions are why i'd like to use OOP to make the manipulation
      // of squares clearer.
      unsigned int GetColumn()
      {
         return m_uiSquareIndex % 8;
      };

      unsigned int GetRow()
      {
         return m_uiSquareIndex / 8;
      };
   };


This way I can use CSquare like this :

Code: Select all
CSquare csq(A1)
csq += 8; // One row higher. csq is now equat to A2.
csq.GetRow(); // Will return 1 (0 based index)
csq.GetColumn(); // will return 0


I think that with basic compiler optimisations like inlining this code will bring no overhead in my engine. I already asked some friends about it and they seem to think like me, but are not sure.

Since it's CC related and there is some good programmers monitoring this board I though I would ask here.

What is your opinion about this?

Mathieu Pag
mathmoi
 
Posts: 37
Joined: 30 Mar 2005, 21:23

Re: Can you confirm that this design do not create an overhe

Postby Volker Annuss » 05 Jan 2006, 19:43

Hello Mathieu,

classes with only a single integer member were not as well optimized as integers, when I compiled them with MS VC++ 6.0 some years ago.

Maybe newer compilers do better. Try it. It is easy to do some basic operations in a loop, once with your class and once with integers and to compare the times.

Greetings,
Volker
Volker Annuss
 
Posts: 49
Joined: 25 Jan 2005, 11:14

Re: Can you confirm that this design do not create an overhe

Postby mathmoi » 05 Jan 2006, 19:51

Volker Annuss wrote:Hello Mathieu,

classes with only a single integer member were not as well optimized as integers, when I compiled them with MS VC++ 6.0 some years ago.

Maybe newer compilers do better. Try it. It is easy to do some basic operations in a loop, once with your class and once with integers and to compare the times.

Greetings,
Volker


Yep, I plan to do this and look at the generated asm. My fear is that it can be well optimized in a simple program and not so well optimized in a complete engine. You know, sometimes compiler optimisations are context dependant.

Mathieu Pag
mathmoi
 
Posts: 37
Joined: 30 Mar 2005, 21:23

Re: Can you confirm that this design do not create an overhe

Postby mathmoi » 05 Jan 2006, 20:40

mathmoi wrote:Yep, I plan to do this and look at the generated asm. My fear is that it can be well optimized in a simple program and not so well optimized in a complete engine. You know, sometimes compiler optimisations are context dependant.

Mathieu Pag?


Hi, I did thoses test I was talking about and I put the results here, in case someone is interested.

Here are the result of my test.

the functions are thoses :

Code: Select all
unsigned int testA(int a)
{
   unsigned int col;
   unsigned int ui = B3;
   ui += a;
   col = ui % 8;
   return col;
}

unsigned int testB(int a)
{
   unsigned int col;
   CCase ccs(B3);
   ccs = ccs + a;
   col = ccs.GetColonne();
   return col;
}


theses two function do the same things : Initialise a square (as un unsigned int in TestA and as a CSquare un TestB), do an arithmetic on it (Add an integer value) and return the column of the resulting index.

MSVC++ generates the same ASM for thoses two functions :

Code: Select all
; 7    :    unsigned int col;
; 8    :    unsigned int ui = B3;
; 9    :    ui += a;

   mov   eax, DWORD PTR _a$[esp-4]
   add   eax, 1

; 10   :    col = ui % 8;

   and   eax, 7

; 11   :    return col;
; 12   : }


and

Code: Select all
; 16   :    unsigned int col;
; 17   :    CCase ccs(B3);
; 18   :    ccs = ccs + a;

   mov   eax, DWORD PTR _a$[esp-4]
   add   eax, 1

; 19   :    col = ccs.GetColonne();

   and   eax, 7

; 20   :    return col;
; 21   : }


I am now convinced that a modern compiler can optimize my CSquare class so there is no overhead in simple functions. I'm still not sure of what will happen in a complete chess engine, but I think it will generate 0 overhead.
mathmoi
 
Posts: 37
Joined: 30 Mar 2005, 21:23

Re: Can you confirm that this design do not create an overhe

Postby Alessandro Scotti » 05 Jan 2006, 23:13

Hi Mathieu,
in Kiwi I have used similar datatypes e.g. for bitboards or moves, although not for squares.
IIRC, it is one of C++ goals to make "small" objects based on native types perform not worse than the native type itself. I have found this to be usually true for release code, but there may be differences when compiling in debug mode.
Since you are running new tests and I did perform mine a while ago, it could be interesting to try also passing a CSquare to a function by value and by reference, and compare the code to an integer parameter.
Also, I think such code as:

Code: Select all
csq += 8;


could be replaced by a ToNextRow() or similar method (just to hide the underlying geometry, which I think is the point of having a square class).
User avatar
Alessandro Scotti
 
Posts: 306
Joined: 20 Nov 2004, 00:10
Location: Rome, Italy

Re: Can you confirm that this design do not create an overhe

Postby Sven Schüle » 06 Jan 2006, 01:42

Hi Mathieu,

what about extending your test code as follows:

Use instances of CSquare within some more complex expressions, e.g. implement a small part of a move generator with it. Avoid using too many constant values in your test, the compiler knows too much about them so you don't get a good answer on your overhead question.

Perhaps compare your definition of CSquare with a simple
Code: Select all
typedef unsigned int CSquare;

where GetRow()/GetColumn() must be defined elsewhere.

Here is a longer example, just by mind (not tested, up to you ;-) ):
Code: Select all
#include <stdio.h>

class CSquare {
    // see your code above

    // extension:
    static inline CSquare make(unsigned int col, unsigned int row) {
        return 8 * row + column;
    }
};

static CSquare A1=0, B1=1, /* ... */ H8=63, IllegalSquare=64;

// could be classes CColour/CPiece as well!
enum Colour { White=0, Black, Empty };
enum Piece { NoPiece=0, Pawn, Knight, Bishop, Rook, Queen, King };

class CBoard {
public: // should be private!
    Piece m_piece[64];
    Colour m_colour[64];

private:
    static Piece c_initialPiece[8];

public:
    inline void setPiece(CSquare s, Piece p, Colour c) {
        m_piece[s] = p;
        m_colour[s] = c;
    }

    inline void removePiece(CSquare s) {
        m_piece[s] = NoPiece;
        m_colour[s] = Empty;
    }

    inline void movePiece(CSquare from, CSquare to) {
        if (m_colour[to] != Empty) {
            removePiece(to);
        }
        setPiece(to, m_piece[from], m_colour[from]);
        removePiece(from);
    }

    inline void clear() {
        for (CSquare s=A1; s<=H8; s++) {
            removePiece(s);
        }
    }

    inline void init() {
        clear();
        for (unsigned int col=0; col<8; col++) {
            setPiece(CSquare::make(0, col), c_initialPiece[col], White);
            setPiece(CSquare::make(1, col), Pawn, White);
            setPiece(CSquare::make(6, col), Pawn, Black);
            setPiece(CSquare::make(7, col), c_initialPiece[col], Black);
        }
    }

    inline CBoard() {
        init();
    }

    // ...
};

Piece CBoard::c_initialPiece[8] = {
    Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook
};

class CMoveGenerator {
private:
    // a possible approach for 64-square board representation:
    CSquare c_knightMoveTargetSquares[64][8];
    CBoard const & m_board;

public:
    CMoveGenerator(CBoard const & board);
    void generateMoves(Colour c); // knows knights only!
    void generateKnightMoves(CSquare from, Colour c);
    void generateSingleMove(CSquare from, CSquare to);
    // ...
};

CMoveGenerator::CMoveGenerator(CBoard const & board)
    : m_board(board)
{
    // init c_knightMoveTargetSquares appropriately
    // I leave it up to you!
}

void CMoveGenerator::generateMoves(Colour c) {
    // oops, no piece list
    for (CSquare from=A1; from<=H8; from++) {
        if (m_board.m_piece[from] == Knight
         && m_board.m_colour[from] == c) {
            generateKnightMoves(from, c);
        }
    }
}

void CMoveGenerator::generateKnightMoves(CSquare from, Colour c) {
    for (unsigned int dir = 0; dir < 8; dir++) {
        CSquare to = c_knightMoveTargetSquares[from][dir];
        if (to != IllegalSquare && m_board.m_colour[to] != c) {
            generateSingleMove(from, to);
        }
    }
}

inline void
CMoveGenerator::generateSingleMove(CSquare from, CSquare to) {
    // dummy
    printf("gen: %c%c-%c%c\n",
        'A'+from.GetColumn(), '1'+from.GetRow(),
        'A'+to.GetColumn(), '1'+to.GetRow());
}

int main() {
    CBoard b;
    CMoveGenerator mg(b);

    b.movePiece(E2, E4);
    b.movePiece(E7, E5);

    b.movePiece(G1, F3);
    mg.generateMoves();

    b.movePiece(G8, F6);
    mg.generateMoves();

    return 0;
}

Just a few small ideas ... what do you think about it?

Sven
User avatar
Sven Schüle
 
Posts: 240
Joined: 26 Sep 2004, 20:19
Location: Berlin, Germany

Re: Can you confirm that this design do not create an overhe

Postby mathmoi » 06 Jan 2006, 15:22

Alessandro Scotti wrote:Hi Mathieu,
in Kiwi I have used similar datatypes e.g. for bitboards or moves, although not for squares.
IIRC, it is one of C++ goals to make "small" objects based on native types perform not worse than the native type itself. I have found this to be usually true for release code, but there may be differences when compiling in debug mode.
Since you are running new tests and I did perform mine a while ago, it could be interesting to try also passing a CSquare to a function by value and by reference, and compare the code to an integer parameter.
Also, I think such code as:

Code: Select all
csq += 8;


could be replaced by a ToNextRow() or similar method (just to hide the underlying geometry, which I think is the point of having a square class).


Hi Alessandro,

I modified my function in order to do the tests you proposed. I had to get rid of a edge effect (you know what that mean? in french : "effet de bord").

My functions are like this:

Code: Select all
unsigned int testA(int a, unsigned int ui)
{
   unsigned int col;
   col = ui + a;
   col += ui % 8;
   return col;
}

unsigned int testB(int a, CCase ccs)
{
   unsigned int col;
   col = ccs + a;
   col += ccs.GetColonne();
   return col;
}


When ccs is not passed as a reference, both function generate the same asm :

Code: Select all
; 15   :    unsigned int col;
; 16   :    col = ccs + a;
; 17   :    col += ccs.GetColonne();

   mov   ecx, DWORD PTR _ccs$[esp-4]
   mov   eax, ecx
   and   eax, 7
   add   eax, DWORD PTR _a$[esp-4]

; 18   :    return col;

   add   eax, ecx

; 19   : }


If I pass ccs as a reference I get one more MOV instruction :

Code: Select all
; 15   :    unsigned int col;
; 16   :    col = ccs + a;
; 17   :    col += ccs.GetColonne();

   mov   eax, DWORD PTR _ccs$[esp-4]
   mov   ecx, DWORD PTR [eax]
   mov   eax, ecx
   and   eax, 7
   add   eax, ecx

; 18   :    return col;

   add   eax, DWORD PTR _a$[esp-4]

; 19   : }


It seems at first that this version take more instructions to execute, but I think that the MOV inserted here is not an overhead since there is a MOV necessary in the "unsigned int" version to copy the ui parameter. I'm not sure of that, but it seem to make sense to me.

So my conclusion is that there is still no overhead even if we pass the CSquare as a reference. However, since sizeof(CSquare) = sizeof(unsigned int) I don't see one would want to pass CSquare as a reference (except if what he want is an out-parameter).

Mathieu Pag
mathmoi
 
Posts: 37
Joined: 30 Mar 2005, 21:23

Re: Can you confirm that this design do not create an overhe

Postby Alessandro Scotti » 06 Jan 2006, 20:52

mathmoi wrote:So my conclusion is that there is still no overhead even if we pass the CSquare as a reference.


Hi Mathieu,
that's good news, thanks for running the test! :-)
User avatar
Alessandro Scotti
 
Posts: 306
Joined: 20 Nov 2004, 00:10
Location: Rome, Italy

Re: Can you confirm that this design do not create an overhe

Postby Alessandro Scotti » 07 Jan 2006, 16:19

With Visual C++ 6.0, it seems the compiler can't use registers with integers wrapped in a class.
Here Move is a small class that has an integer as its only data member.

Code: Select all
Move get_move1() {
    Move result( 0 );
    return result;
}

compiles into:
Code: Select all
mov eax, DWORD PTR ___$ReturnUdt$[esp-4]
mov DWORD PTR [eax], 0

but
Code: Select all
unsigned get_move2() {
    unsigned result = 0;
    return result;
}

becomes just:
Code: Select all
xor eax, eax
User avatar
Alessandro Scotti
 
Posts: 306
Joined: 20 Nov 2004, 00:10
Location: Rome, Italy


Return to Programming and Technical Discussions

Who is online

Users browsing this forum: No registered users and 4 guests