The other day I was debugging ChessKISS looking for strange calls to system in order to increase performance and surprisingly I found too many calls to GetMem() , very strange I thought, I check the call stack and I end up in a very innocent function called ReceiveDataFromWinboard
function TChessEngine.ReceiveDataFromWinboard(out aMove: TMove): TMoveStatus; var cmd: TString; notification: TNotification<AnsiString>; begin aMove := NO_MOVE; result := msNone; if FWinboardTask = nil then begin notification := FWinboard.GetMessage; FWinboardTask := TFuture<AnsiString>.Create(notification, tpNormal); end; if FWinboardTask.Available then begin try cmd := TString(FWinboardTask.GetValue); if FWinboardTask.Error <> '' then begin FLog.Add(FWinboardTask.Error); raise FWinboardTask.GetException; end; if (not IsCommand(cmd)) and (FGameStatus = gsPlay) then result := TCommandHandler.Instance.ParseCommand(cmd, aMove); finally FreeAndNil(FWinboardTask); end; end; end;
Again nothing to be worried, a new thread is created every time the remote application sends a message, so I had to disassembler in order to see what was going on and voila!
004A0F38 55 push ebp 004A0F39 8BEC mov ebp,esp 004A0F3B 6A00 push $00 004A0F3D 53 push ebx 004A0F3E 56 push esi 004A0F3F 8BD8 mov ebx,eax 004A0F41 33C0 xor eax,eax 004A0F43 55 push ebp 004A0F44 68AF0F4A00 push $004a0faf 004A0F49 64FF30 push dword ptr fs:[eax] 004A0F4C 648920 mov fs:[eax],esp 004A0F4F B201 mov dl,$01 004A0F51 A1880E4A00 mov eax,[$004a0e88] 004A0F56 E8393EF6FF call TObject.Create 004A0F5B 8BF0 mov esi,eax 004A0F5D 8D45FC lea eax,[ebp-$04] 004A0F60 8BD6 mov edx,esi 004A0F62 85D2 test edx,edx 004A0F64 7403 jz $004a0f69 004A0F66 83EAF8 sub edx,-$08 004A0F69 E84E84F6FF call @IntfCopy
WTF!?, Delphi is automatically creating an object whose name is xxx$ActRec and then copying some information. Well, we have to isolated the problem and see if this still happening or it is a combination of closures with threads.procedure TForm39.OnTimer(Sender: TObject);
begin if FCount = 0 then begin FCount := 100; Call( function: integer begin result := Random(100); end ); end else Dec(FCount); end; procedure TForm39.Call(aTest: TTest); begin aTest(); end; procedure TForm39.FormCreate(Sender: TObject); begin FCount := 0; end
So here we go, in this example a Timer is calling a function which ONLY calls a closure once every 100 times, so I checked again the assembler code generated and I got very disappointed, Delphi automatically does his stuff every time there is a closure function, usually this is more than ok on a regular application, but on an application like ChesskISS which is heavily based on threads and the main loop is called millions of times per second this is not acceptable.
The solution was to add a new constructor in the TFuture<T> class adding a simple notificator as a parameter, so the code was clean again, sadly CheckKISS still slow, shame on me...