C# Performance

Discussion of chess software programming and technical issues.

Moderators: hgm, Rebel, chrisw

RoadWarrior
Posts: 73
Joined: Fri Jan 13, 2012 12:39 am
Location: London, England

Re: C# Performance

Post by RoadWarrior »

Richard Allbert wrote:I've been through all functions with the IL DASM and box is not used anywhere - I looked in Attacks, Perft, MoveGen, MoveMake
From Eric Lippert's very useful essay [1] on value type storage:
Eric Lippert wrote:It is frequently the case that array elements, fields of reference types, locals in an iterator block and closed-over locals of a lambda or anonymous method must live longer than the activation period of the method that first required the use of their storage. And even in the rare cases where their lifetimes are shorter than that of the activation of the method, it is difficult or impossible to write a compiler that knows that. Therefore we must be conservative: all of these storage locations go on the heap.
So if my understanding is correct, using an array or List to store the moves will always mean heap allocations. Therefore it looks like you don't have a performance issue caused by boxing. We need to look elsewhere. For example, Sven's stack-based suggestion seems like a promising approach.

[1] http://blogs.msdn.com/b/ericlippert/arc ... types.aspx
There are two types of people in the world: Avoid them both.
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

Yes, I think so too.

If you downloaded the Solution file, please do so again, as I've condesnsed it down properly into one main ENgine lib, and a console and perft lib.

Thanks

Richard
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

Ok, implemented using a movelist created on startUp -

Code: Select all

public class Board
{
        public int[] FirstMoveAtPly = new int[SearchConstants.MaxPly];
        public Move[] MoveList = new Move[Moves.MaxMoveListMoves];
..
}

in MoveGen class..

private static void AddMove(Board board, int f, int t, int prom, int cap, int fl)
        {
            int index = board.GetMoveIndex();
            board.MoveList[index].from = f;
            board.MoveList[index].to = t;
            board.MoveList[index].promotedTo = prom;
            board.MoveList[index].captured = cap;
            board.MoveList[index].flag = fl;
            board.IncrementMoveIndex();
        }

this yielded

Code: Select all

Preinitiliased moveList + struct Move
3000 knps, 900kB Heap Allocations 
So a MASSIVE reduction in heap allocation, but slower than the Array + class Move version. There were no GC collections!
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

Profile is below:

Image
RoadWarrior
Posts: 73
Joined: Fri Jan 13, 2012 12:39 am
Location: London, England

Re: C# Performance

Post by RoadWarrior »

Richard Allbert wrote:Ok, implemented using a movelist created on startUp -

Code: Select all

public class Board
{
        public int[] FirstMoveAtPly = new int[SearchConstants.MaxPly];
        public Move[] MoveList = new Move[Moves.MaxMoveListMoves];
..
}

in MoveGen class..

private static void AddMove(Board board, int f, int t, int prom, int cap, int fl)
        {
            int index = board.GetMoveIndex();
            board.MoveList[index].from = f;
            board.MoveList[index].to = t;
            board.MoveList[index].promotedTo = prom;
            board.MoveList[index].captured = cap;
            board.MoveList[index].flag = fl;
            board.IncrementMoveIndex();
        }

this yielded

Code: Select all

Preinitiliased moveList + struct Move
3000 knps, 900kB Heap Allocations 
So a MASSIVE reduction in heap allocation, but slower than the Array + class Move version. There were no GC collections!
No collections because the move list is now anchored to the Board class, and won't be collected until the Board class goes out of scope.

The AddMove and IncrementMoveIndex methods are taking 8% of your time, with nearly 2% taken just incrementing the move index! Can you get line timings for these methods, because it might be related to the slowdown you're seeing versus the previous local move list.

Can you make FirstMoveAtPly and MoveList internal and static, rather than instance fields?
There are two types of people in the world: Avoid them both.
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

ok!

One thing I noticed from the profiler was in MakeMove, the Historylist.Add() was taking a lot of the time.

I changed it from

Code: Select all

public List<MoveHistory> gameHistory = new List<MoveHistory>&#40;SearchConstants.MaxMovesInGame&#41;;
to

Code: Select all

        public MoveHistory&#91;&#93; gameHistory = new MoveHistory&#91;SearchConstants.MaxMovesInGame&#93;;
        public int HistoryMoves = 0;
and made the changes needed to makeMove, and the speed went from the 3000knps to 3700knps.....
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

One thing about making those static... if I ever get to SMP, I'll need more than one copy of the position, won't I? If so, then the movelist can't be static? Or am I missing something?
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

Some more changes, from the profiler.

I took out unnecessary Reference / Variable definitions in Make and Unmake,
and I changed the board piecelist defintion to hold an index on the piece list for a given square: (As Sven suggested)


Code: Select all


        public int&#91;&#93; pceListIndexFromSq = new int&#91;BoardConstants.BoardSquareCount&#93;;

private static void RemoveFromPieceList&#40;Board board, int pce, int square&#41;
        &#123;
            Debug.Assert&#40;Squares.SquareOnBoard&#40;square&#41; == true&#41;;
            Debug.Assert&#40;Pieces.PieceIsValid&#40;pce&#41; == true&#41;;

            int end = &#40;BoardConstants.StartIndex&#91;pce&#93; + board.pceCount&#91;pce&#93;) - 1;
            int index = board.pceListIndexFromSq&#91;square&#93;;

            board.pceListIndexFromSq&#91;square&#93; = Squares.OffBoard;
          

            if &#40;index != end&#41;
            &#123;
                board.pceLst&#91;index&#93; = board.pceLst&#91;end&#93;;
                board.pceListIndexFromSq&#91;board.pceLst&#91;index&#93;&#93; = index;
            &#125;

            board.pceLst&#91;end&#93; = Squares.OffBoard;
            board.pceCount&#91;pce&#93;--;
        &#125;

private static void MovePieceOnList&#40;Board board, int pce, int fromsq, int tosq&#41;
        &#123;
            Debug.Assert&#40;Squares.SquareOnBoard&#40;fromsq&#41; == true&#41;;
            Debug.Assert&#40;Squares.SquareOnBoard&#40;tosq&#41; == true&#41;;
            Debug.Assert&#40;Pieces.PieceIsValid&#40;pce&#41; == true&#41;;

            int start = BoardConstants.StartIndex&#91;pce&#93;;
            int end = &#40;start + board.pceCount&#91;pce&#93;) - 1;

            int index = board.pceListIndexFromSq&#91;fromsq&#93;;
            board.pceLst&#91;index&#93; = tosq;
            board.pceListIndexFromSq&#91;fromsq&#93; = Squares.OffBoard;
            board.pceListIndexFromSq&#91;tosq&#93; = index;
        &#125;

public static void AddPieceToList&#40;Board board, int pce, int square&#41;
        &#123;
            Debug.Assert&#40;Squares.SquareOnBoard&#40;square&#41; == true&#41;;
            Debug.Assert&#40;Pieces.PieceIsValid&#40;pce&#41; == true&#41;;

            int newIndex = board.pceCount&#91;pce&#93; + BoardConstants.StartIndex&#91;pce&#93;;
            board.pceListIndexFromSq&#91;square&#93; = newIndex;
            board.pceLst&#91;newIndex&#93; = square;
            board.pceCount&#91;pce&#93;++;
        &#125;
Test run showed 4100 knps, so things are moving in the right direction!
Last edited by Richard Allbert on Wed Feb 08, 2012 5:09 pm, edited 1 time in total.
Richard Allbert
Posts: 792
Joined: Wed Jul 19, 2006 9:58 am

Re: C# Performance

Post by Richard Allbert »

And the profile...

Image

And the source....

Source
RoadWarrior
Posts: 73
Joined: Fri Jan 13, 2012 12:39 am
Location: London, England

Re: C# Performance

Post by RoadWarrior »

Richard Allbert wrote:Test run showed 4100 knps, so things are moving in the right direction!
This is promising as you've more than doubled the speed. Are you profiling the optimised release build with all the settings set for max speed? The only non-optimal setting you might need is /pdbonly so that the profiler can measure performance at the line level.

Making stuff static might indeed interfere with later SMP work. The idea is to test whether it makes a significant difference. If it does make a difference, then you need to make a decision about when the SMP work is going to happen. In my engine, I'm kicking that can down the street for at least a year, given my 1-2 hours a day of development time.
There are two types of people in the world: Avoid them both.