Performance Delphi接口性能问题
我对我的文本编辑器做了一些非常认真的重构。现在代码更少了,扩展组件也更容易了。我大量使用了OO设计,例如抽象类和接口。然而,我注意到在性能方面有一些损失。问题在于读取大量记录。当所有事情都发生在同一个对象内时,速度很快,但通过接口完成时速度很慢。我制作了最简单的程序来说明细节:Performance Delphi接口性能问题,performance,delphi,oop,interface,Performance,Delphi,Oop,Interface,我对我的文本编辑器做了一些非常认真的重构。现在代码更少了,扩展组件也更容易了。我大量使用了OO设计,例如抽象类和接口。然而,我注意到在性能方面有一些损失。问题在于读取大量记录。当所有事情都发生在同一个对象内时,速度很快,但通过接口完成时速度很慢。我制作了最简单的程序来说明细节: unit Unit3; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Di
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.
单元3;
接口
使用
窗口、消息、系统工具、变体、类、图形、控件、窗体、,
对话;
常数
N=10000000;
类型
记录
Val1,Val2,Val3,Val4:整数;
结束;
TArrayOfRecord=树索阵列;
IMyInterface=接口
['{C0070757-2376-4A5B-AA4D-CA7EB058501A}']
函数GetArray:TArrayOfRecord;
属性Arr:tarrayofRecordReadGetArray;
结束;
TMyObject=class(TComponent,IMyInterface)
受保护的
FArr:TArrayOfRecord;
公众的
程序初始化;
函数GetArray:TArrayOfRecord;
结束;
TForm3=类别(TForm)
过程表单创建(发送方:ToObject);
私有的
{私有声明}
公众的
{公开声明}
结束;
变量
表3:TForm3;
MyObject:TMyObject;
实施
{$R*.dfm}
程序TForm3.FormCreate(发送方:TObject);
变量
i:整数;
v1,v2,f:Int64;
MyInterface:IMyInterface;
开始
MyObject:=TMyObject.Create(Self);
尝试
MyObject.InitArr;
如果不是MyObject.GetInterface(IMyInterface,MyInterface),那么
引发异常。Create('Note to self:Typo in the code');
查询性能计数器(v1);
//方法1:无接口(快速!)
//对于i:=0到高(MyObject.FArr)do
//if(MyObject.FArr[i].Val1
当我直接读取数据时,我得到的时间大约为0.14秒。但当我通过界面时,需要1.06秒
这种新设计是否无法达到与以前相同的性能
我应该提到的是,我试图设置
PArrayOfRecord=^TArrayOfRecord
,重新定义IMyInterface.arr:PArrayOfRecord
,并在for
循环中写入arr^
等。这帮助很大;然后我得到了0.22秒。但这仍然不够好。是什么让它开始时这么慢?在遍历元素之前,只需将数组分配给一个局部变量
您看到的是接口方法调用是虚拟的,必须通过间接方式调用。此外,代码必须通过一个“thunk”来修复“Self”引用,以指向对象实例,而不是接口实例
通过只调用一个虚拟方法来获取动态数组,您可以从循环中消除这种开销。现在,您的循环可以遍历数组项,而无需虚拟接口方法调用的额外开销。您正在比较oranges和Apple,因为第一个测试读取字段(FArr),而第二个测试读取分配了getter的属性(Arr)。唉,接口不提供对其字段的直接访问,所以您真的不能像以前那样以任何其他方式进行访问。 但正如Allen所说,这会导致对getter方法(GetArray)的调用,该方法被归类为“虚拟”方法,而您甚至没有编写它,因为它是接口的一部分。 因此,每次访问都会导致VMT查找(通过接口间接进行)和方法调用。 此外,使用动态数组的事实意味着调用方和被调用方都将进行大量引用计数(如果查看生成的汇编代码,您可以看到这一点) 所有这些都足以解释测量的速度差,但使用局部变量并仅读取一次数组确实可以轻松克服。当您这样做时,对getter的调用(以及所有随后的引用计数)只发生一次。与测试的其余部分相比,这个“开销”变得无法测量
但请注意,一旦您走上这条路线,您将失去封装,对数组内容的任何更改都不会反映回接口中,因为数组具有写时拷贝行为。只是一个警告。您的设计使用了巨大的内存。优化你的界面
IMyInterface = interface
['{C0070757-2376-4A5B-AA4D-CA7EB058501A}']
function GetCount:Integer:
function GetRecord(const Index:Integer):TRecord;
property Record[Index:Integer]:TRecord read GetRecord;
end;
答案都是完全正确的
然而,由于您的问题涉及到改进的OO设计,我觉得您的设计中的一个特别的改变也会提高性能,因此可以进行讨论
设置标记的代码“非常有控制性”。我的意思是,为了计算标记值,你花了很多时间“在另一个对象内部(通过一个接口)”摸索。这实际上就是暴露“性能问题”的原因
IMyInterface = interface
['{C0070757-2376-4A5B-AA4D-CA7EB058501A}']
function GetArray: TArrayOfRecord;
function GetTagValue: Integer; //<-- Add and implement this
property Arr: TArrayOfRecord read GetArray;
end;
function TMyObject.GetTagValue: Integer;
var
I: Integer;
begin
for i := 0 to High(FArr) do
if (FArr[i].Val1 < FArr[i].Val2) or
(FArr[i].Val3 < FArr[i].Val4) then
begin
Result := FArr[i].Val1 + FArr[i].Val2 -
FArr[i].Val3 + FArr[i].Val4;
end;
end;
Tag := MyInterface.GetTagValue;