在Delphi 理智中,人们使用class
来定义对象
在Turbo Pascal for Windows中,我们使用object
,今天您仍然可以使用object
来创建对象。
不同之处在于,object
存在于堆栈中,而class
存在于堆上。
当然object
是折旧的。
把所有这些放在一边:
使用object
代替课程,是否有好处,速度明智?
我知道object
在Delphi 2009中被破坏了,但我有一个特殊用例 1)其中速度很重要而且我试图找到是否使用object
会使我的事情变得更快而不会让它变得越来越多
这个代码库是在Delphi 7中,但我可能将它移植到Delphi 2007,还没有决定。
1)康威的生命游戏
长评
谢谢大家指出我正确的方向。
让我再解释一下。我正在尝试更快地实施 hashlife ,see also here或here for simple sourcecode
目前的记录保持者是golly,但是他使用Bill Gospher原始lisp代码的直接翻译(这是一种很好的算法,但根本没有在微观层面进行优化)。 Hashlife使您能够在O(log(n))时间内计算生成。
它通过使用空格/时间权衡来实现。由于这个原因,hashlife需要大量内存,千兆字节并非闻所未闻。作为回报,您可以在o(1)时间内使用第2代^ 127(170141183460469231731687303715880000000)计算第2代128(340282366920938463463374607431770000000)。
因为hashlife需要为更大模式中出现的所有子模式计算哈希值,所以对象的分配需要很快。
这是我已经解决的解决方案:
分配优化
我分配了一大块物理内存(用户可设置),比方说512MB。在这个blob中我分配了我称之为奶酪堆栈的东西。这是我推送和弹出的普通堆栈,但是pop也可以来自堆栈的中间。如果发生这种情况,我会在free
列表上标记(这是一个正常的堆栈)。推送时,如果没有空闲,我先检查free
列表,我正常推送。我将使用记录,因为它看起来像具有最少开销的解决方案。
由于hashlife的工作方式,很少pop
ping发生,并且有很多push
es。我为不同大小的结构保留单独的堆栈,确保在4/8/16字节边界上保持内存访问对齐。
其他优化
inline
答案 0 :(得分:13)
对于使用普通OOP编程,应始终使用class
类。你将拥有Delphi中最强大的对象模型,包括接口和泛型(在后来的Delphi版本中)。
<强> 1。记录,指针和对象
记录可能是邪恶的(如果您忘记将参数声明为const
,则缓慢隐藏副本,记录隐藏的慢速清理代码,fillchar
将记录任何字符串变成内存泄漏...),但它们有时通过指针访问二进制结构(例如某些“小值”)非常方便。
微小记录的动态数组(例如,使用一个整数和一个双字段)将比<{1}}小类多快;使用our TDynArray
wrapper,您可以对记录进行高级访问,包括序列化,排序,散列等。
如果使用指针,您必须知道自己在做什么。如果你想使用神奇的“VCL组件所有权模型”,最好坚持使用类,TList
。
记录不允许继承。您需要使用“变体记录”(在其类型定义中使用TPersistent
关键字),或者使用嵌套记录。使用类C语言API时,有时您必须使用面向对象的结构。使用嵌套记录或变体记录是恕我直言,而不是好旧的“对象”继承模型。
<强> 2。何时使用对象
但是在某些地方,对象是访问现有数据的好方法。
即使对象模型也比新记录模型好,因为它处理简单的继承。
在a Blog entry last summer中,我发布了一些仍然使用对象的可能性:
一个内存映射文件,我想非常快速地解析:指向这样一个对象的指针很棒,你手边还有方法;我将它用于TFileHeader或TFileInfo,它映射.zip标题,在SynZip.pas中;
一个Win32结构,由API调用定义,我在其中放置了方便的方法来轻松访问数据(为此你可以使用记录,但如果结构中有一些面向对象 - 这是非常的常见 - 你必须嵌套记录,这不是很方便);
在堆栈上定义的临时结构,仅在过程中使用:我将它用于SynZip.pas中的TZStream,或者用于我们的RTTI相关类,它们以面向对象的方式映射Delphi生成的RTTI而不是作为TypeInfo,它是面向函数/过程的。通过直接映射RTTI内存内容,我们的代码比使用在堆上创建的新RTTI类更快。我们没有实现任何内存,对于像我们这样的ORM框架来说,它的速度是有益的。我们需要大量的RTTI信息,但我们需要它,我们需要它。
第3。现代Delphi中如何破坏对象实现
现代德尔福中的对象被破坏的事实是一种耻辱,恕我直言。
通常,如果在堆栈上定义一个记录,包含一些引用计数变量(如字符串),它将在方法/函数的开始级别由一些编译器魔术代码初始化:
case
那些type TObj = object Int: integer; Str: string; end;
procedure Test;
var O: TObj
begin // here, an _InitializeRecord(@O,TypeInfo(TObj)) call is made
O.Str := 'test';
(...)
end; // here, a _FinalizeRecord(@O,TypeInfo(TObj)) call is made
和_InitializeRecord
将“准备”然后“释放”O.Str变量。
使用Delphi 2010,我发现有时候,这个_InitializeRecord()并不总是如此。 如果记录只有一些没有公共字段,则隐藏调用有时不会由编译器生成。
再次构建源代码,将会......
我发现的唯一解决方案是使用record关键字而不是object。
以下是生成的代码的样子:
_FinalizeRecord
/// used to store and retrieve Words in a sorted array
// - is defined either as an object either as a record, due to a bug
// in Delphi 2010 compiler (at least): this structure is not initialized
// if defined as a record on the stack, but will be as an object
TSortedWordArray = {$ifdef UNICODE}record{$else}object{$endif}
public
Values: TWordDynArray;
Count: integer;
/// add a value into the sorted array
// - return the index of the new inserted value into the Values[] array
// - return -(foundindex+1) if this value is already in the Values[] array
function Add(aValue: Word): PtrInt;
/// return the index if the supplied value in the Values[] array
// - return -1 if not found
function IndexOf(aValue: Word): PtrInt; {$ifdef HASINLINE}inline;{$endif}
end;
非常糟糕......但是自...以来没有发生代码生成错误。
源代码中的结果修改并不大,但有点令人失望。我发现旧版本的IDE(例如Delphi 6/7)无法解析此类声明,因此类层次结构将在编辑器中被破坏...... :(
向后兼容性应包括回归测试。由于现有代码,许多Delphi用户都会使用此产品。对于Delphi未来来说,破解功能是非常有问题的,恕我直言:如果你必须重写很多代码,你不应该只将项目切换到C#或Java?
答案 1 :(得分:7)
Object
不是设置对象的Delphi 1方法;这是设置对象的短命Turbo Pascal方法,它被Delphi 1中的Delphi TObject模型取代。它保持了向后兼容性,但应该避免出于以下几个原因:
object
做得那么好,所以你不会在这里失去任何东西。至于问题的其余部分,并没有那么多的速度优势。 TObject模型速度非常快,特别是如果你使用FastMM内存管理器加速对象的创建和销毁,如果你的对象包含很多字段,它们甚至比很多情况下的记录更快,因为它们'通过引用传递,不必为每个函数调用复制。
答案 2 :(得分:6)
当选择“快速和可能破碎”和“快速正确”时,总是选择后者。
旧式对象不提供普通旧记录的速度激励,因此无论您何时想要使用旧式对象,都可以使用记录而不存在未初始化的编译器管理类型或破坏的虚拟方法的风险。如果你的Delphi版本不支持带方法的记录,那么只需使用独立的程序。
答案 3 :(得分:1)
回到旧版本的Delphi,它不支持使用方法的记录,然后使用object
是在堆栈上分配对象的方法。偶尔会产生有价值的性能优势。现在record
更好。 record
中缺少的唯一功能是能够从另一个record
继承。
当您从class
更改为record
时,您会放弃很多,所以只有在性能优势太大的情况下才会考虑它。