Why does creating a TRttiContext in advance make my RTTI tests run faster?
Asked Answered
K

1

3

Linked to the original question Is it possible to get the index of class property? and answered by Remy Lebeau and RRUZ

program Demo;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, Winapi.Windows,
  System.Rtti, System.TypInfo;

type
  TMyClass = class
  private
    function GetInteger(const Index: Integer): Integer;
    procedure SetInteger(const Index, Value: Integer);
  public
    property P1: Integer Index 1 read GetInteger write SetInteger;
    property P2: Integer Index 2 read GetInteger write SetInteger;
    property P3: Integer Index 3 read GetInteger write SetInteger;
  end;

{ TMyClass }

function TMyClass.GetInteger(const Index: Integer): Integer;
begin
  Result := Index;
end;

procedure TMyClass.SetInteger(const Index, Value: Integer);
begin
  //
end;

{------------------------------------------------------------------------------}
function GetPropertyIndex(const AClass: TClass; APropertyName: string): Integer;
var
  Ctx: TRttiContext;
begin
  Ctx := TRttiContext.Create;
  Result := (Ctx.GetType(AClass).GetProperty(APropertyName) as TRttiInstanceProperty).Index;
end;

{------------------------------------------------------------------------------}
var
  C: Cardinal;
  I: Integer;
  N: Integer;
begin
  try
    C := GetTickCount;
    for N := 1 to 1000000 do
      I := GetPropertyIndex(TMyClass, 'P2');
    WriteLn(GetTickCount - C, ' ms - The index of the property P2 is ', I);

    ReadLn;
  except
    on E: Exception do Writeln(E.ClassName, ': ', E.Message);
  end;
end.

On my PC this test takes ~ 5 sec. But when I use TRttiContext before calling GetPropertyIndex() - for example

...
begin
  try
    TRttiContext.Create.Free;

    C := GetTickCount;
    for N := 1 to 1000000 do
      I := GetPropertyIndex(TMyClass, 'P2');
...

same test takes only ~ 1 sec. Why ?

Edit The problem I found when I tested secon example as

...
var
  Ctx: TRttiContext;
  C: Cardinal;
  I: Integer;
  N: Integer;
begin
  try
    C := GetTickCount;
    for N := 1 to 1000000 do
    begin
      Ctx := TRttiContext.Create;
      I := (Ctx.GetType(TMyClass).GetProperty('P2') as TRttiInstanceProperty).Index;
    end;  
    WriteLn(GetTickCount - C, ' ms - The index of the property P2 is ', I);

    ReadLn;
  except
    on E: Exception do Writeln(E.ClassName, ': ', E.Message);
  end;
end.

But the reason is more obvious in the second test as is the question.

Kraska answered 1/11, 2013 at 7:16 Comment(6)
The two versions of code appear to be identical. Be more careful. Also you never need to call TRttiContext.Create, or to free it. You may as well declare a global variable of that type.Mammiemammiferous
@DavidHeffernan - in the second test is TRttiContext.Create.Free; in first not. It is added only to illustrate the problem. I edit my question, I have added the original test - it is more logical, but reason is not so obviousKraska
If you want to compare two different pieces of code, you should present those two pieces of code. It's still not totally clear. My advice stands. Don't ever both calling TrttiContext.Create or Free. If you need to use TRttiContext, just put it in a global variable.Mammiemammiferous
BTW, TRTTIContext is AFAIR record, not class :-)Ordzhonikidze
@DavidHeffernan - Probably I was not clear enough, my english is poor, but I got the answer. And thank you for your interest.Kraska
I know you got your answer. I'm trying to encourage you to be more precise and clear in your questions. This helps you as much as it helps us.Mammiemammiferous
P
11

RTTI data is cached using a reference counted pool. The reason your code speeds up so much is because the TRttiContext.Create.Free statement creates a temp TRttiContext that stays in scope for the lifetime of your app, maintaining an active reference to that pool. Subsequent TRttiContext instances created inside of GetPropertyIndex() are re-using the existing pool and not wasting time re-creating new pools each time. When the DPR code reaches the end. statement, the temp TRttiContext goes out of scope and releases its reference to the pool.

When you remove the TRttiContext.Create.Free statement, the temp TRttiContext disappears, and a new pool is created and destroyed for each TRttiContext that GetPropertyIndex() uses, wasting time re-creating the same cache over and over.

You can easily see this in action by enabling Debug DCUs in the Project Options and then stepping into the TRttiContext code in the debugger.

Pastiness answered 1/11, 2013 at 8:47 Comment(3)
Thank you again. Your answers are always very helpful and educational!Kraska
I often use TRttiContext. Do you have any doubts if I would create a global TRttiContext at the app initialization and later use only this one?Kraska
@Kraska that should be perfectly fine, provided you are not dynamically loading/unloading packages while that TRTTIContext is alive, requiring the RTTI cache to be updated.Pastiness

© 2022 - 2024 — McMap. All rights reserved.