Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi Interface Performance Issue

Tags:

I have done some really serious refactoring of my text editor. Now there is much less code, and it is much easier to extend the component. I made rather heavy use of OO design, such as abstract classes and interfaces. However, I have noticed a few losses when it comes to performance. The issue is about reading a very large array of records. It is fast when everything happens inside the same object, but slow when done via an interface. I have made the tinyest program to illustrate the details:

unit Unit3;  interface  uses   Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,   Dialogs;  const   N = 10000000;  type   TRecord = record     Val1, Val2, Val3, Val4: integer;   end;    TArrayOfRecord = array of TRecord;    IMyInterface = interface   ['{C0070757-2376-4A5B-AA4D-CA7EB058501A}']     function GetArray: TArrayOfRecord;     property Arr: TArrayOfRecord read GetArray;   end;    TMyObject = class(TComponent, IMyInterface)   protected     FArr: TArrayOfRecord;   public     procedure InitArr;     function GetArray: TArrayOfRecord;   end;    TForm3 = class(TForm)     procedure FormCreate(Sender: TObject);   private     { Private declarations }   public     { Public declarations }   end;  var   Form3: TForm3;   MyObject: TMyObject;  implementation  {$R *.dfm}  procedure TForm3.FormCreate(Sender: TObject); var   i: Integer;   v1, v2, f: Int64;   MyInterface: IMyInterface; begin    MyObject := TMyObject.Create(Self);    try     MyObject.InitArr;      if not MyObject.GetInterface(IMyInterface, MyInterface) then       raise Exception.Create('Note to self: Typo in the code');      QueryPerformanceCounter(v1);      // APPROACH 1: NO INTERFACE (FAST!)   //  for i := 0 to high(MyObject.FArr) do   //    if (MyObject.FArr[i].Val1 < MyObject.FArr[i].Val2) or   //         (MyObject.FArr[i].Val3 < MyObject.FArr[i].Val4) then   //      Tag := MyObject.FArr[i].Val1 + MyObject.FArr[i].Val2 - MyObject.FArr[i].Val3   //               + MyObject.FArr[i].Val4;     // END OF APPROACH 1       // APPROACH 2: WITH INTERFACE (SLOW!)         for i := 0 to high(MyInterface.Arr) do       if (MyInterface.Arr[i].Val1 < MyInterface.Arr[i].Val2) or            (MyInterface.Arr[i].Val3 < MyInterface.Arr[i].Val4) then         Tag := MyInterface.Arr[i].Val1 + MyInterface.Arr[i].Val2 - MyInterface.Arr[i].Val3                  + MyInterface.Arr[i].Val4;     // END OF APPROACH 2      QueryPerformanceCounter(v2);     QueryPerformanceFrequency(f);     ShowMessage(FloatToStr((v2-v1) / f));    finally      MyInterface := nil;     MyObject.Free;    end;   end;  { TMyObject }  function TMyObject.GetArray: TArrayOfRecord; begin   result := FArr; end;  procedure TMyObject.InitArr; var   i: Integer; begin   SetLength(FArr, N);   for i := 0 to N - 1 do     with FArr[i] do     begin       Val1 := Random(high(integer));       Val2 := Random(high(integer));       Val3 := Random(high(integer));       Val4 := Random(high(integer));     end; end;  end. 

When I read the data directly, I get times like 0.14 seconds. But when I go through the interface, it takes 1.06 seconds.

Is there no way to achieve the same performance as before with this new design?

I should mention that I tried to set PArrayOfRecord = ^TArrayOfRecord and redefined IMyInterface.arr: PArrayOfRecord and wrote Arr^ etc in the for loop. This helped a lot; I then got 0.22 seconds. But it is still not good enough. And what makes it so slow to begin with?

like image 301
Andreas Rejbrand Avatar asked Oct 18 '10 19:10

Andreas Rejbrand


1 Answers

Simply assign the array to a local variable before iterating through the elements.

What you're seeing is that the interface methods calls are virtual and have to be called through an indirection. Also, the code has to pass-through a "thunk" that fixes up the "Self" reference to now point to the object instance and not the interface instance.

By making only one virtual method call to get the dynamic array, you can eliminate that overhead from the loop. Now your loop can go through the array items without the extra overhead of the virtual interface method calls.

like image 132
Allen Bauer Avatar answered Oct 06 '22 01:10

Allen Bauer