0

I want to refactor DelphiAST to use interfaces to deal with different types, rather than the clunky TDirectionary it uses now.

Some research shows that 70%+ of the running time is spend in the dictionary.

So I'll make interfaces like:

TSyntaxNode = class
  ...
end;

IIdentifier = interface
  ['{D72E945D-E397-4E02-B702-8AE6C235F9C6}']
  function GetIdentifier: string;
  property Identifier: string read GetIdentifier;
end;

IMethod = interface(Identifier)
  ['{8E6119DC-E0F3-42BD-A5BF-FB658E34499E}']
  .....
end;

TMethodNode = class(TSyntaxNode, IMethod, IIdentifier,...)
 ...
end;

The problem according to Roman is:

Reference counting may cause performance issues. DelphiAST creates thousands of classes to produce the syntax tree (more than 100,000 of TSyntaxNode instances, when input file is big enough). How many times the reference counter would be called?

Every time that happens a hidden try finally is invoked and that will slow things way down.

Strict use of const in method params prevents the refcount code calling the method, but afaik it still happens every time you do something like, say, MyRef = List[0] - it will increment the refcount assigning to MyRef, even though the item is still present in the list.

How can I work with interfaces whilst not having to worry about refcounting and try-finally blocks?
I'm perfectly happy to manage destruction of classes manually.

Further info
I'm guessing I need to use TAggregatedObject as a base ancestor.
And I read somewhere that not assigning a GUID inhibits reference counting, but have to source to back that up.
However losing the GUID's would lead to problems in obtaining sub-interfaces so I'd have to devise a solution to that....

Johan
  • 74,508
  • 24
  • 191
  • 319
  • Here's some info on how to use records to **implement** interfaces: https://sergworks.wordpress.com/2012/04/01/interfaces-without-objects/ But I want the interface part of the interface to go away, so the try-finally's go away. – Johan Apr 26 '15 at 19:40
  • Declare `MyRef` as a `Pointer`. The assignment statement will work. When you need to access the interface's methods, type-cast it to the desired interface type. Type-casting doesn't incur reference counting. – Rob Kennedy Apr 27 '15 at 00:31
  • 1
    You are optimizing into the wrong direction. The way to optimize the code in DelphiAST is to use polymorphism because that is what it's for. – Stefan Glienke Apr 27 '15 at 10:51
  • Why does DelphiAST even uses generics? – SilverWarior Apr 28 '15 at 07:15
  • @SilverWarior,generics do not cause slowness, perhaps code-bloat, but not slowness. – Johan Apr 28 '15 at 15:26
  • @Johan My although limited experience with generics tell me otherwise. – SilverWarior Apr 28 '15 at 20:48

3 Answers3

2

Can I use interfaces without invoking hidden try-finally's?

No. The compiler emits reference counting code with interfaces no matter what. You cannot avoid it.

You can implement you own version of interfaces using a record of function pointers. It will be more clunky but will avoid heap allocation and reference counting.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

"Thousands of objects" always gives me a shiver. There is a significant overhead to an object in memory. You forget about it, but it pops up again when you're trying to manage thousands, or notice you loose performance on it, or start to try writing or reading from file...

Using interfaces won't change much as far as I can tell, since you still use objects (class instances) underneath.

Endeavours of this magnitude require specific use of good-old straight-to-memory data-structures. For example I've been playing with an AST stored in an array of records: https://github.com/stijnsanders/strato

Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67
  • That not really an answer to the question, but I'm curious about your solution. Btw the slowness of DelphiAST in its current form is due to the use of TDictionary, not primary due to the creation of too many instances of TSyntaxNode. – Johan Apr 28 '15 at 18:37
  • You using arrays of records, which carry the exact same refcount issues as interfaces. But do not support polymorphism, so whilst interesting will not help with parsing Delphi code with its complex structure. – Johan Apr 28 '15 at 18:45
  • Hmm, I didn't know there was such demand for this. Now I feel like I should have a go at this myself. With all units of a project and a symbol table... (Then continue to figure out this LLVM-IR stuff...) – Stijn Sanders Apr 29 '15 at 06:26
0

Yes No you cancannot use interfaces without invoking try-finally's and refcounting.
You can however greatly reduce the number of hidden exception handlers.
You just have to be really careful to do two things.

  1. Always use const parameters when passing interfaces.

  2. Never store the interface in an interface type variable, but use a homebrew record to encapsulate the interface so that its refcount will not be touched.

Here's a sample of the encapsulating record:

type
  TInterface<Intf: IInterface> = record
  private
    P: Pointer;
  public
    function I: Intf; inline;
    class operator Implicit(const A: Intf): TInterface<Intf>; inline;
  end;

function TInterface<Intf>.I: Intf;
begin
  pointer(IInterface(Result)):= P;
end;

class operator TInterface<Intf>.Implicit(const A: Intf): TInterface<Intf>;
begin
  Result.P:= pointer(IInterface(A));
end;

Here's a sample program to demonstrate the concept.

program Project32;
{$APPTYPE CONSOLE}

{$R *.res}
uses
  System.SysUtils;

type
  TInterface<Intf: IInterface> = record
  private
    P: Pointer;
  public
    function I: Intf; inline;
    class operator Implicit(const A: Intf): TInterface<Intf>; inline;
  end;

  ITest1 = interface
    function Test1: integer;
  end;

  ITest2 = interface
    function Test2: integer;
  end;

  TTest = class(TAggregatedObject, ITest1, ITest2)
    function Test1: integer;
    function Test2: integer;
  end;

{ TTest }

function TTest.Test1: integer;
begin
  Result:= 1;
end;

function TTest.Test2: integer;
begin
  Result:= 2;
end;

{ TInterface<Intf> }

function TInterface<Intf>.I: Intf;
begin
  pointer(IInterface(Result)):= P;
end;

class operator TInterface<Intf>.Implicit(const A: Intf): TInterface<Intf>;
begin
  Result.P:= pointer(IInterface(A));
end;

var
  I1: TInterface<ITest1>;
  I2: TInterface<ITest2>;
  Test: TTest;

begin
  Test:= TTest.Create(nil);  //Force AV on _AddRef, _Release
  If (Test.Test1 = 1) then WriteLn(S);
  I1:= Test;
  If (I1.I.Test1 =1) then WriteLn(S);
  I2:= Test;
  If (I2.I.Test2 = 2) then WriteLn(S);
  ReadLn(s);
  Test.Free;
end.

The TAggregatedObject does not have a interface to handle the _AddRef/_Release calls.
During the lifetime of the program, no problems will occur, however Delphi does wrap the creation of TTest in a try-finally which will generate an exception when exiting the function.

In real-world use you'd have to use a TInterfacedObject. If you pass the interface references around a lot it might help though.

Johan
  • 74,508
  • 24
  • 191
  • 319