среда, 14 октября 2015 г.

#852. Exceptions and efficiency

Original in Russian: http://programmingmindstream.blogspot.ru/2015/09/1165.html

I’ve profiled my scripts for efficiency (the problem roots in here - http://programmingmindstream.blogspot.ru/2015/09/1164-aqtime.html (in Russian)) and was surprised (again) to find out that (often) throwing the exception in business logic causes multiple loss in efficiency.

For example, the following code:

ARRAY FUNCTION LIST
 OBJECT IN anObject
 ^ IN aFunctor
  
 OBJECT VAR l_Element
 l_Element := anObject
 Result := [
  while true
  begin
   l_Element := ( l_Element aFunctor DO )
   if ( l_Element pop:object:IsNil ) then
    BREAK
   l_Element 
  end
 ]
; // LIST

works many times slower that the analogue:

ARRAY FUNCTION LIST
 OBJECT IN anObject
 ^ IN aFunctor
  
 OBJECT VAR l_Element
 l_Element := anObject
 BOOLEAN VAR l_NeedDo
 l_NeedDo := true
 Result := [
  while l_NeedDo
  begin
   l_Element := ( l_Element aFunctor DO )
   if ( l_Element pop:object:IsNil ) then
   begin
    l_NeedDo := false
   end
   else
    l_Element 
  end
 ]
; // LIST

Why is it so?

The reason is that actually BREAK is organized as follows:

https://bitbucket.org/lulinalex/mindstream/src/7deb4ed1ebc5a138c2a90cc69f14bed0847b09a1/Examples/1165/BasicsPack.pas?at=B284&fileviewer=file-view-default

procedure TkwBREAK.DoDoIt(const aCtx: TtfwContext);
//#UC START# *4DAEEDE10285_9FA400CD8713_var*
//#UC END# *4DAEEDE10285_9FA400CD8713_var*
begin
//#UC START# *4DAEEDE10285_9FA400CD8713_impl*
 raise EtfwBreak.Create('Loop exit');
//#UC END# *4DAEEDE10285_9FA400CD8713_impl*
end;//TkwBREAK.DoDoIt

Basic time is “chewed” by object creation/deletion which is actually “rather costly”.

Here is the assembly code:



That is why I used to do my own object cach.

Here is an example:

https://bitbucket.org/lulinalex/mindstream/src/7deb4ed1ebc5a138c2a90cc69f14bed0847b09a1/Examples/1165/l3UnknownPrim.imp.pas?at=B284&fileviewer=file-view-default

class function _l3UnknownPrim_.NewInstance: TObject;
  //override;
  {* - memory allocation function for object’s instance; it is overridden to check the memory for the objects. }
{$IfDef _UnknownNeedL3}
var
 l_System : Tl3System;
{$EndIf _UnknownNeedL3}
begin
 {$IfDef _UnknownNeedL3}
 l_System := Tl3System(g_l3System);
 if (l_System = nil) then
 begin
  if not l3MemUtilsDown{l3SystemDown} then
  begin
   l_System := l3System;
//   if (l_System <> nil) then
//    l_System.Stack2Log('Possible oddness NewInstance/FreeInatance');
  end;//not l3SystemDown
 end;//l_System = nil
 Assert((l_System <> nil) OR not Cacheable); 
 if (l_System <> nil) AND l_System.CanCache AND Cacheable then
 begin
  Result := GetFromCache;
  if (Result <> nil) then
  begin
   _l3UnknownPrim_(Result).InitAfterAlloc;
   Exit;
  end;//Result <> nil
 end;{l_System.CanCache}
 {$EndIf _UnknownNeedL3}
 Result := AllocInstanceMem;
 _l3UnknownPrim_(Result).Use;
 _l3UnknownPrim_(Result).InitAfterAlloc;
 {$IfDef _UnknownNeedL3}
 {$IfDef l3TraceObjects}
 if (l_System <> nil) then
  l_System.RegisterObject(Result, Cacheable);
 {$EndIf l3TraceObjects}
 {$EndIf _UnknownNeedL3}
end;

In this case I made exceptions as singletons and used them in the following way:

procedure TkwBREAK.DoDoIt(const aCtx: TtfwContext);
//#UC START# *4DAEEDE10285_9FA400CD8713_var*
//#UC END# *4DAEEDE10285_9FA400CD8713_var*
begin
//#UC START# *4DAEEDE10285_9FA400CD8713_impl*
 raise EtfwBreak.Instance;
//#UC END# *4DAEEDE10285_9FA400CD8713_impl*
end;//TkwBREAK.DoDoIt

In some degree, it solved the issue of efficiency.

Sure, this is all about “scripts”.

Indeed, I have found it at large working data sizes like the large model project (dozens of thousands classes and about 12-15 millions lines of code).

One would think Delphi developer does not care about it.

I just want to stress that “throwing exceptions” is a “costly” trick. Unnecessary use of exceptions in business logic (I have seen people doing so) as a “special function result” inevitably leads to efficiency loss.

It is so when exceptions are thrown relatively often compared to the “usual code”.

I would also like to write about ARC and exceptions but I’d rather won’t.

I do NOT think you will understand me right. ARC is mainstream, after all.

Though, as judged by the code, exceptions are ALSO exposed to ARC.

However, it seems the Embarcadero developers have not faced these problems yet.

Thus, let’s consider these thoughts as “my personal phantom pain”.


Комментариев нет:

Отправить комментарий