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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With