And*_*and 24 delphi oop performance interface
我已经对我的文本编辑器进行了一些非常严肃的重构.现在代码少得多,扩展组件要容易得多.我大量使用OO设计,例如抽象类和接口.但是,在性能方面,我注意到了一些损失.问题在于阅读大量的记录.当一切都发生在同一个对象内部时速度很快,但通过接口完成时速度很慢.我制作了最简单的程序来说明细节:
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.
Run Code Online (Sandbox Code Playgroud)
当我直接读取数据时,我得到0.14秒的时间.但是当我通过界面时,需要1.06秒.
使用这种新设计是否无法达到与之前相同的性能?
我应该提一下,我试图在循环中设置PArrayOfRecord = ^TArrayOfRecord
和重新定义IMyInterface.arr: PArrayOfRecord
并编写Arr^
等for
.这有很大帮助; 然后我得到了0.22秒.但它仍然不够好.是什么让它开始这么慢?
All*_*uer 28
在迭代元素之前,只需将数组赋值给局部变量即可.
您所看到的是接口方法调用是虚拟的,必须通过间接调用.此外,代码必须传递一个"thunk"来修复"Self"引用,现在指向对象实例而不是接口实例.
通过只进行一次虚拟方法调用来获取动态数组,可以消除循环中的开销.现在,您的循环可以遍历数组项,而无需虚拟接口方法调用的额外开销.
您将橘子与苹果进行比较,因为第一个测试读取字段(FArr),而第二个测试读取具有分配了它的吸气剂的属性(Arr).唉,接口不能直接访问他们的字段,所以你真的不能像你一样做任何其他方式.但正如艾伦所说,这会导致调用getter方法(GetArray),它被归类为"虚拟"而你甚至没有写出来,因为它是接口的一部分.因此,每次访问都会导致VMT查找(通过接口间接)和方法调用.此外,您使用动态数组这一事实意味着调用者和被调用者都会进行大量的引用计数(如果您查看生成的汇编代码,可以看到这一点).
所有这些已经足以解释测量的速度差异,但是使用局部变量确实可以轻松克服并且只读取一次数组.当你这样做时,对getter的调用(以及所有随后的引用计数)只发生一次.与其他测试相比,这种"开销"变得无法测量.
但是请注意,一旦你走这条路线,你就会松开封装,对数组内容的任何改变都不会反映回接口,因为数组具有写时复制行为.只是一个警告.