【问题标题】:Is there a benefit in using old style `object` instead of `class` in Delphi?在 Delphi 中使用旧样式的 `object` 而不是 `class` 有什么好处吗?
【发布时间】:2011-05-23 22:28:16
【问题描述】:

在 Delphi 理智中,人们使用 class 来定义对象。
在 Windows 的 Turbo Pascal 中,我们使用了 object,而今天您仍然可以使用 object 创建对象。

区别在于object 位于堆栈上,而class 位于堆上。
当然,object 已贬值。

抛开所有这些:

使用object 代替class 是否有利于提高速度?

我知道 object 在 Delphi 2009 中已损坏,但我有一个特殊用例1),其中速度很重要,我正在尝试查找是否使用 @987654333 @ 会让我的东西更快而不会出错
此代码库在 Delphi 7 中,但我可能会将其移植到 Delphi 2007,尚未决定。


1)康威的人生游戏

长评论
感谢大家为我指明正确的方向。

让我再解释一下。我正在尝试更快地实现 hashlifesee also herehere for simple sourcecode

目前的记录保持者是golly,但 golly 使用了 Bill Gospher 原始 lisp 代码的直接翻译(作为一种算法非常出色,但在微观层面根本没有优化)。 Hashlife 使您能够在 O(log(n)) 时间内计算出一代。

它通过使用空间/时间权衡来做到这一点。出于这个原因,hashlife 需要大量内存,千兆字节并非闻所未闻。作为回报,您可以在 o(1) 时间内使用第 2^127 代 (170141183460469231731687303715880000000) 计算第 2^128 代 (340282366920938463463374607431770000000)。

因为 hashlife 需要为更大模式中出现的所有子模式计算哈希,所以对象的分配需要快速。

这是我确定的解决方案:

分配优化
我分配了一大块物理内存(用户可设置),比如说 512MB。在这个 blob 中,我分配了我称之为 cheese stacks 的东西。这是一个普通的堆栈,我在其中推送和弹出,但弹出也可以来自堆栈的中间。如果发生这种情况,我会将其标记在free 列表中(这是一个普通堆栈)。推送时,我首先检查free 列表,如果没有可用的内容,我会正常推送。我将按照建议使用记录,它看起来是开销最少的解决方案。

由于 hashlife 的工作方式,popping 很少发生,pushes 发生很多。我为不同大小的结构保留单独的堆栈,确保在 4/8/16 字节边界上保持内存访问对齐。

其他优化

  • 递归删除
  • 缓存优化
  • 使用inline
  • 哈希预计算(类似于彩虹表)
  • 病理病例检测及回退算法的使用
  • GPU 的使用

【问题讨论】:

  • 我会考虑记录而不是旧式对象类型。
  • 我相信问题有答案。使用基于堆栈的对象,您可以节省堆分配、取消分配,您的对象只需更改堆栈指针。
  • 如果你不能用基于类的对象足够快地运行康威的生活游戏,那么你做错了很多事情。我敢打赌,您甚至可以在堆上分配一个对象而不是在开始时生成的原始对象集之外编写整个内容。例如,在棋盘上的每个方格分配一个对象是错误的。板应该由一个二维数组表示,如果你愿意,你可以将它包装在一个类中。一个实例是整个“游戏板”。您需要花时间进行这些优化(决策)。忘记对象。它已经死了。
  • @Warren,我已经有了一个最优算法(hashlife)。所以我已经完成了那个阶段,请不要假设任何东西。
  • 不假设的东西。我要明确说明:TObjects 的堆分配在您的性能中将是可以忽略不计的因素,否则,您做错了。

标签: delphi class object delphi-7 delphi-2007


【解决方案1】:

要使用普通的 OOP 编程,您应始终使用 class 类型。您将拥有 Delphi 中最强大的对象模型,包括接口和泛型(在以后的 Delphi 版本中)。

1.记录、指针和对象

记录可能是邪恶的(如果忘记将参数声明为const,则记录隐藏的慢速清理代码,fillchar 会使记录中的任何字符串成为内存泄漏...),但它们有时非常方便通过指针访问二进制结构(例如一些“小值”)。

小记录的动态数组(例如,一个整数和一个双精度字段)将比TList 的小类快;使用our TDynArray wrapper,您将拥有对记录的高级访问权限,包括序列化、排序、散列等。

如果使用指针,你必须知道你在做什么。如果您想使用神奇的“VCL 组件所有权模型”,那么坚持使用类肯定更可取,TPersistent

记录不允许继承。您需要使用“变体记录”(在其类型定义中使用 case 关键字),或者使用嵌套记录。使用类 C API 时,有时必须使用面向对象的结构。恕我直言,使用嵌套记录或变体记录远不如旧的“对象”继承模型清晰。

2。何时使用对象

但在某些地方,对象是访问现有数据的好方法。

甚至对象模型也比新的记录模型更好,因为它处理简单的继承。

a Blog entry last summer,我发布了一些仍然使用对象的可能性:

  • 一个内存映射文件,我想快速解析它:指向这样一个对象的指针非常棒,而且你手头还有方法;我将它用于在 SynZip.pas 中映射 .zip 标头的 TFileHeader 或 TFileInfo;

  • 一个由 API 调用定义的 Win32 结构,我在其中放置了方便的方法来轻松访问数据(为此,您可以使用记录,但如果结构中有一些面向对象 - 这是非常常见 - 你必须嵌套记录,这不是很方便);

  • 在堆栈上定义的临时结构,仅在过程中使用:我将其用于 SynZip.pas 中的 TZStream,或用于我们的 RTTI 相关类,它们以面向对象的方式映射 Delphi 生成的 RTTI,而不是作为面向函数/过程的 TypeInfo。通过直接映射 RTTI 内存内容,我们的代码比使用在堆上创建的新 RTTI 类更快。我们不实例化任何内存,对于像我们这样的 ORM 框架来说,这有利于它的速度。我们需要很多 RTTI 信息,但我们需要它快点,我们直接需要它。

3.现代 Delphi 中如何破坏对象实现

在现代 Delphi 中对象被破坏的事实是一种耻辱,恕我直言。

通常,如果您在堆栈上定义一条记录,包含一些引用计数变量(如字符串),它将由一些编译器魔术代码在方法/函数的开始级别初始化:

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_FinalizeRecord 将“准备”然后“释放” O.Str 变量。

在 Delphi 2010 中,我发现有时并不总是生成这个 _InitializeRecord()。 如果记录只有一些没有公共字段,隐藏调用有时不是由编译器生成的。

只要重新编译源码,就会有……

我发现的唯一解决方案是使用记录关键字而不是对象。

结果代码如下所示:

/// 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;

{$ifdef UNICODE}record{$else}object{$endif} 很糟糕……但此后没有发生代码生成错误……

对源代码的修改并不大,但有点令人失望。我发现旧版本的 IDE(例如 Delphi 6/7)无法解析这样的声明,因此编辑器中的类层次结构将被破坏...... :(

向后兼容性应包括回归测试。很多 Delphi 用户因为现有的代码而停留在这个产品上。破坏性功能对于 Delphi 的未来非常有问题,恕我直言:如果您必须重写大量代码,您不应该将项目切换到 C# 或 Java 吗?

【讨论】:

  • @Bouchez,感谢您提供非常有帮助的答案。对于我的用例(没有字符串),对象似乎没有损坏,但我将使用 record 因为我决定不使用堆栈。
  • Free Pascal 中的 Afaik 他们现在已初始化。
【解决方案2】:

Object 不是 Delphi 1 设置对象的方法;它是建立对象的短暂 Turbo Pascal 方法,在 Delphi 1 中被 Delphi TObject 模型取代。为了向后兼容而保留它,但出于以下几个原因应该避免使用它:

  1. 正如您所指出的,它在最近的版本中被破坏了。而且 AFAIK 没有修复它的计划。
  2. 这是一个概念上错误的对象模型。面向对象编程的全部要点(它与过程编程真正区别开来的一件事)是 Liskov 替换(继承和多态),并且继承和值类型不能混合使用。
  3. 您失去了对许多需要 TObject 后代的功能的支持。
  4. 如果您确实需要不需要动态分配和初始化的值类型,您可以使用记录来代替。你不能从他们那里继承,但你也不能很好地使用object,所以你不会在这里失去任何东西。

至于问题的其余部分,速度优势并没有那么多。 TObject 模型非常快,特别是如果您使用 FastMM 内存管理器来加速对象的创建和销毁,并且如果您的对象包含大量字段,它们在很多情况下甚至可以比记录更快,因为它们'通过引用重新传递,不必为每个函数调用复制。

【讨论】:

  • 我认为,如果您为记录/对象参数添加const,则不会在每次函数调用时复制其内容。只有当您将记录/对象用作函数 result 时,该副本才可用。在这种情况下,当您使用记录/对象时,最好使用var 参数而不是函数结果。
  • 我同意你所说的大部分内容,但请注意,任何recordobject 也是通过引用传递的。好的,如果该项目未作为 const 传递,则在函数的 prolog 中进行复制。
【解决方案3】:

在“快速且可能损坏”和“快速且正确”之间做出选择时,请始终选择后者。

与普通的旧记录相比,旧式对象不提供速度激励,因此无论您在哪里可能想使用旧式对象,都可以使用记录来代替,而不会有未初始化的编译器管理的类型或损坏的虚拟方法的风险。如果您的 Delphi 版本不支持带有方法的记录,则只需使用独立过程即可。

【讨论】:

  • +1 表示快速和正确。性能始终是次要问题。快速搞砸,快速做错,永远比做对更好。
  • 记录的问题是它不处理继承。许多普通的公共 API 结构,甚至一些自定义结构都是面向对象的,因此需要这种继承。变体记录(带有“case integer”)不是很优雅,嵌套记录也不是一个好的选择。
  • @A.Bouchez,自 90 年代以来,旧式对象中的继承一直存在缺陷——从某种意义上说,编译器会默默地生成不正确的代码。如果不优雅是正确编译代码的代价,我会每次都付出代价。
  • 通过简单的对象继承,我从来没有遇到过这样的问题。错误的 AFAIK 的虚拟方法,但对于静态方法,从来没有生成不正确的代码。你是对的,有时不优雅是值得的......为了规避编译器问题......所以在这种情况下我们不应该受到责备! ;)
【解决方案4】:

在旧版本的 Delphi 中,不支持使用方法记录的方法,然后使用 object 是在堆栈上分配对象的方法。偶尔会产生有价值的性能优势。现在record 更好。 record 唯一缺少的功能是能够从另一个 record 继承。

当您从 class 更改为 record 时,您放弃了很多,所以只有在性能优势非常巨大时才考虑它。

【讨论】:

  • @David 他们是,hashlife 的幼稚实现将 90% 的时间花在内存密集型(启动)fase 分配内存中。
  • 记录继承并不是一个缺失的特性,它是一个非特性,它弊大于利。继承类型和值类型不能混合使用,因为当派生类型添加新字段时,您最终会遇到各种参数传递问题。看看 C++ 的“复制构造函数”就知道这个模型有多丑了。
  • @Mason 我不同意。我确实同意 C++ 模型非常复杂,但是他们能够为他们的对象(例如字符串)分配堆栈,而我们其他人必须使用堆并付出代价。我在极少数地方成功地使用了object 继承。
  • 将堆栈用于字符串的价格也不总是“免费”的。坦斯塔夫。
  • @warren 我猜你放弃了写副本。
猜你喜欢
  • 1970-01-01
  • 2020-12-22
  • 1970-01-01
  • 2012-11-29
  • 2014-08-28
  • 2011-12-09
  • 2012-05-18
  • 2011-10-29
  • 1970-01-01
相关资源
最近更新 更多