我可以在不调用隐藏的 try-finally 的情况下使用接口吗
Posted
技术标签:
【中文标题】我可以在不调用隐藏的 try-finally 的情况下使用接口吗【英文标题】:Can I use interfaces without invoking hidden try-finally's 【发布时间】:2015-04-26 19:11:34 【问题描述】:我想refactor DelphiAST使用接口来处理不同的类型,而不是笨拙的 它现在使用的 TDirectionary。
Some research 表明 70% 以上的运行时间都花在了字典上。
所以我会做这样的接口:
TSyntaxNode = class
...
end;
IIdentifier = interface
['D72E945D-E397-4E02-B702-8AE6C235F9C6']
function GetIdentifier: string;
property Identifier: string read GetIdentifier;
end;
IMethod = interface(Identifier)
['8E6119DC-E0F3-42BD-A5BF-FB658E34499E']
.....
end;
TMethodNode = class(TSyntaxNode, IMethod, IIdentifier,...)
...
end;
根据 Roman 的问题是:
引用计数可能会导致性能问题。 DelphiAST 创建了数千个类来生成语法树(当输入文件足够大时,超过 100,000 个 TSyntaxNode 实例)。引用计数器会被调用多少次?
每次发生这种情况时都会调用隐藏的try finally
,这会减慢速度。
在方法参数中严格使用
const
可防止引用计数代码调用该方法,但是每次您执行诸如MyRef = List[0]
之类的操作时它仍然会发生 - 它会增加分配给MyRef
的引用计数,甚至尽管该项目仍然存在于列表中。
如何使用接口而不用担心 refcounting 和 try-finally 块? 我非常乐意手动管理类的破坏。
更多信息
我猜我需要使用 TAggregatedObject
作为基础祖先。
我在某处读到,不分配 GUID 会抑制引用计数,但必须提供资源来支持它。
但是丢失 GUID 会导致获取子接口时出现问题,所以我必须为此设计一个解决方案....
【问题讨论】:
这里有一些关于如何使用记录来实现接口的信息:sergworks.wordpress.com/2012/04/01/interfaces-without-objects 但我希望接口的接口部分消失,所以 try-finally 消失了. 将MyRef
声明为Pointer
。赋值语句将起作用。当您需要访问接口的方法时,将其类型转换为所需的接口类型。类型转换不会产生引用计数。
您的优化方向错误。在 DelphiAST 中优化代码的方法是使用多态,因为这就是它的用途。
为什么 DelphiAST 甚至使用泛型?
@SilverWarior,泛型不会导致缓慢,可能是代码膨胀,但不会导致缓慢。
【参考方案1】:
我可以在不调用隐藏的 try-finally 的情况下使用接口吗?
没有。无论如何,编译器都会发出带有接口的引用计数代码。你无法避免它。
您可以使用函数指针记录实现您自己的接口版本。它会更笨重,但会避免堆分配和引用计数。
【讨论】:
是的,可以,我记得在一个 delphi 博客中看到过类似的东西,但不记得在哪里。 只需声明一个记录,其成员是函数指针。手动实现的接口。【参考方案2】:“万千物件”总是让我不寒而栗。内存中的对象有很大的开销。您忘记了它,但是当您尝试管理数千个时,它会再次弹出,或者注意到您的性能下降,或者开始尝试从文件中写入或读取...
据我所知,使用接口不会有太大变化,因为您仍然在下面使用对象(类实例)。
这种规模的努力需要专门使用古老的直接内存数据结构。例如,我一直在使用存储在记录数组中的 AST:https://github.com/stijnsanders/strato
【讨论】:
这并不是问题的真正答案,但我很好奇您的解决方案。顺便说一句,当前形式的 DelphiAST 的缓慢是由于使用了 TDictionary,而不是主要由于创建了太多 TSyntaxNode 实例。 您使用记录数组,它们携带与接口完全相同的引用计数问题。但不支持多态性,因此虽然有趣,但对于解析结构复杂的 Delphi 代码无济于事。 嗯,我不知道有这样的需求。现在我觉得我应该自己尝试一下。一个项目的所有单元和一个符号表...(然后继续弄清楚这个LLVM-IR的东西...)【参考方案3】:是不,你可以不调用try-finally
和引用计数就不能使用接口。
但是,您可以大大减少隐藏异常处理程序的数量。
你只需要非常小心地做两件事。
在传递接口时始终使用const
参数。
永远不要将接口存储在接口类型变量中,而是使用自制记录来封装接口,这样它的引用计数就不会被触及。
以下是封装记录的示例:
type
TInterface<Intf: IInterface> = record
private
P: Pointer;
public
function I: Intf; inline;
class operator Implicit(const A: Intf): TInterface<Intf>; inline;
end;
function TInterface<Intf>.I: Intf;
begin
pointer(IInterface(Result)):= P;
end;
class operator TInterface<Intf>.Implicit(const A: Intf): TInterface<Intf>;
begin
Result.P:= pointer(IInterface(A));
end;
这是一个演示该概念的示例程序。
program Project32;
$APPTYPE CONSOLE
$R *.res
uses
System.SysUtils;
type
TInterface<Intf: IInterface> = record
private
P: Pointer;
public
function I: Intf; inline;
class operator Implicit(const A: Intf): TInterface<Intf>; inline;
end;
ITest1 = interface
function Test1: integer;
end;
ITest2 = interface
function Test2: integer;
end;
TTest = class(TAggregatedObject, ITest1, ITest2)
function Test1: integer;
function Test2: integer;
end;
TTest
function TTest.Test1: integer;
begin
Result:= 1;
end;
function TTest.Test2: integer;
begin
Result:= 2;
end;
TInterface<Intf>
function TInterface<Intf>.I: Intf;
begin
pointer(IInterface(Result)):= P;
end;
class operator TInterface<Intf>.Implicit(const A: Intf): TInterface<Intf>;
begin
Result.P:= pointer(IInterface(A));
end;
var
I1: TInterface<ITest1>;
I2: TInterface<ITest2>;
Test: TTest;
begin
Test:= TTest.Create(nil); //Force AV on _AddRef, _Release
If (Test.Test1 = 1) then WriteLn(S);
I1:= Test;
If (I1.I.Test1 =1) then WriteLn(S);
I2:= Test;
If (I2.I.Test2 = 2) then WriteLn(S);
ReadLn(s);
Test.Free;
end.
TAggregatedObject
没有处理_AddRef
/_Release
调用的接口。
在程序的生命周期中,不会发生任何问题,但是 Delphi 确实将 TTest
的创建包装在 try-finally 中,这将在退出函数时产生异常。
在实际使用中,您必须使用 TInterfacedObject。如果您通过很多接口引用它可能会有所帮助。
【讨论】:
以上是关于我可以在不调用隐藏的 try-finally 的情况下使用接口吗的主要内容,如果未能解决你的问题,请参考以下文章
第9项:尽量使用try-with-resources而不是try-finally(Prefer try-with-resources to try-finally)