Showing posts with label Optimizations. Show all posts
Showing posts with label Optimizations. Show all posts

Monday, September 5, 2011

Trying Delphi XE2, some performance tests, part 2

I had the feeling that the compiler does some strange optimizations somewhere since ChessKISS is slower in the 64 bits version, in order to prove that I've created the following test:


type
  TForm44 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FA,
    FB,
    FC,
    FD: NativeInt;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form44: TForm44;

implementation

{$R *.dfm}

procedure TForm44.FormCreate(Sender: TObject);
var
  a, b, c, d: NativeInt;

begin
  a := 1;
  FA := a * 2;

  b := FA * 4;
  FB := b * 8;

  c := FB * 16;
  FC := c * 32;

  d := FC * 64;
  FD := d * 128;

  Caption := IntToStr(FD);
end;

end.
The optimized Win32 version:
Unit44.pas.31: a := 1;
0050FE73 B801000000       mov eax,$00000001
Unit44.pas.32: fa := a * 2;
0050FE78 8BD0             mov edx,eax
0050FE7A 03D2             add edx,edx
0050FE7C 899390030000     mov [ebx+$00000390],edx
Unit44.pas.33: b := fa * 4;
0050FE82 8BC2             mov eax,edx
0050FE84 03C0             add eax,eax
0050FE86 03C0             add eax,eax
Unit44.pas.34: fb := b * 8;
0050FE88 8BD0             mov edx,eax
0050FE8A 03D2             add edx,edx
0050FE8C 03D2             add edx,edx
0050FE8E 03D2             add edx,edx
0050FE90 899394030000     mov [ebx+$00000394],edx
Unit44.pas.35: c := fb * 16;
0050FE96 8BC2             mov eax,edx
0050FE98 C1E004           shl eax,$04
Unit44.pas.36: fc := c * 32;
0050FE9B 8BD0             mov edx,eax
0050FE9D C1E205           shl edx,$05
0050FEA0 899398030000     mov [ebx+$00000398],edx
Unit44.pas.37: d := fc * 64;
0050FEA6 8BC2             mov eax,edx
0050FEA8 C1E006           shl eax,$06
Unit44.pas.38: fd := d * 128;
0050FEAB 8BF0             mov esi,eax
0050FEAD C1E607           shl esi,$07
0050FEB0 89B39C030000     mov [ebx+$0000039c],esi
A more or less good optimization, let's see the win64 version:
Unit44.pas.31: a := 1;
000000000059C535 C7C001000000     mov eax,$00000001
Unit44.pas.32: FA := a * 2;
000000000059C53B 488D0400         lea rax,[rax+rax]
000000000059C53F 488B4D20         mov rcx,[rbp+$20]
000000000059C543 48898150060000   mov [rcx+$00000650],rax
Unit44.pas.33: b := FA * 4;
000000000059C54A 488B8150060000   mov rax,[rcx+$00000650]
000000000059C551 4803C0           add rax,rax
000000000059C554 4803C0           add rax,rax
Unit44.pas.34: FB := b * 8;
000000000059C557 488D04C500000000 lea rax,[rax*8+$0000]
000000000059C55F 488B4D20         mov rcx,[rbp+$20]
000000000059C563 48898158060000   mov [rcx+$00000658],rax
Unit44.pas.35: c := FB * 16;
000000000059C56A 488B8158060000   mov rax,[rcx+$00000658]
000000000059C571 48C1E004         shl rax,$04
Unit44.pas.36: FC := c * 32;
000000000059C575 48C1E005         shl rax,$05
000000000059C579 488B4D20         mov rcx,[rbp+$20]
000000000059C57D 48898160060000   mov [rcx+$00000660],rax
Unit44.pas.37: d := FC * 64;
000000000059C584 488B8160060000   mov rax,[rcx+$00000660]
000000000059C58B 48C1E006         shl rax,$06
Unit44.pas.38: FD := d * 128;
000000000059C58F 48C1E007         shl rax,$07
000000000059C593 488B4D20         mov rcx,[rbp+$20]
000000000059C597 48898168060000   mov [rcx+$00000668],rax
At a first glance it seems ok, but a closer look (check in blue) shows some strange results, Self accesses are not well optimized (I've noticed that also in ChessKISS), it keeps saving/reading the current value in rax register, looks like it could be easily kept on some register (and you have many in x64)

Friday, November 12, 2010

ChessKISS code optimizations

I've lately been doing a lot of optimizations in the code that I want to share with you (although the main speed up always comes from reducing the tree size...)

class TKillers

Before:

FHits: array[0..63, 0..63, 0..63] of integer; //Depth,From,To


After:


FHits: array[0..63, 0..$ffff] of integer; //Depth,From&To

Since the index is already pack as 16 bits now we use that directly as index, so a new function was created in TMoveHelper:

class function TMoveHelper.GetIndex(aMove: TMove): integer;
begin
  //This function RETURNS 16 bits [0..65535] not 6 bits [0..63] as index
  //Max will be $4040 [63][63]
  Exit(aMove shr 16);
end;
The same rule applies to THistoric:
type
  THistoric = class
  private
    FHistory: array[0..$ffff] of integer; //$4040
  public
    constructor Create;
    procedure Add(aMove: TMove; aDepth: integer);
    function Get(aMove: TMove): integer; inline;
    procedure Clear;
  end;

procedure THistoric.Add(aMove: TMove; aDepth: integer);
begin
  if (aMove <> 0) and (not TMoveHelper.IsCaptureOrPromote(aMove)) then
    Inc(FHistory[TMoveHelper.GetIndex(aMove)], aDepth);
end;

function THistoric.Get(aMove: TMove): integer;
begin
  if aMove = 0 then
    Exit(0)
  else
    Exit(FHistory[TMoveHelper.GetIndex(aMove)]);
end;
In order to speed up the moves creation we have a few tables that helps checking 
when a move is outside the board without "if's". 
The function is TBoardchess.OutOfBoard(aIndex, aDir: TDir): boolean;
Before it was using a map of integers, now I've change it to booleans:
Map: array[0..143] of boolean =
  (
    True, True, True,  True,  True,  True,  True,  True,  True,  True,  True, True, //0
    True, True, True,  True,  True,  True,  True,  True,  True,  True,  True, True, //12
    True, True, False, False, False, False, False, False, False, False, True, True, //24
    True, True, False, False, False, False, False, False, False, False, True, True, //36
    True, True, False, False, False, False, False, False, False, False, True, True, //48
    True, True, False, False, False, False, False, False, False, False, True, True, //64
    True, True, False, False, False, False, False, False, False, False, True, True, //72
    True, True, False, False, False, False, False, False, False, False, True, True, //84
    True, True, False, False, False, False, False, False, False, False, True, True, //96
    True, True, False, False, False, False, False, False, False, False, True, True, //108
    True, True, True,  True,  True,  True,  True,  True,  True,  True,  True, True, //120
    True, True, True,  True,  True,  True,  True,  True,  True,  True,  True, True  //132
  );

Before outside was 0 and inside was 1, so the output assembler is now:
function OutOfBoard(aIndex: integer; aDir: TDir): boolean; inline; 
begin
  Exit(Map[MapLookup[aIndex] + Offsets12[aDir]]); 
end;

ASM:
004A68D0 8B049538054B00   mov eax,[edx*4+$4b0538]
004A68D7 0FB6D1           movzx edx,cl
004A68DA 03049538064B00   add eax,[edx*4+$4b0638]
004A68E1 0FB680A8044B00   movzx eax,[eax+$004b04a8]
Before it was:
Unit34.pas.593: Exit(Map[MapLookup[aIndex] + Offsets12[aDir]] = 0);
004A68D0 8B0495E8064B00   mov eax,[edx*4+$4b06e8]
004A68D7 0FB6D1           movzx edx,cl
004A68DA 030495E8074B00   add eax,[edx*4+$4b07e8]
004A68E1 833C85A8044B0000 cmp dword ptr [eax*4+$4b04a8],$00
004A68E9 0F94C0           setz al
So a line less and no branching millions times always count... ( I hope :-) )
Revisiting the move generation I could remove some extra lines in 
all move/capture functions:
procedure TChessboard.GenerateKingMoves(aPiece: TPiece; aIndex: integer; var aMoves: TMoveSet);
var
  i, dst, src: integer;

begin
  src := aPiece.Index;
  i := 7;
  while i >= 0 do
  begin
    if not OutOfBoard(src, TDir(i)) then
    begin
      dst := Offsets8Cols[TDir(i)] + src;
      if (GetPiece(dst) = nil) and ((aIndex = -1) or (dst = aIndex)) then
        aMoves.Add(TMoveHelper.Pack(ptKing, src, dst, actMove));
    end;

    Dec(i);
  end;
end;