8

在我们正在开发的 Delphi 应用程序中,我们有一个庞大的相关对象结构。这些对象的某些属性具有在运行时计算的值,我正在寻找一种方法来缓存结果以进行更密集的计算。我使用的一种方法是在第一次计算时将值保存在私有成员中。这是一个简短的示例:

unit Unit1;

interface

type
  TMyObject = class
  private
    FObject1, FObject2: TMyOtherObject;
    FMyCalculatedValue: Integer;
      function GetMyCalculatedValue: Integer;
  public
    property MyCalculatedValue: Integer read GetMyCalculatedValue;
  end;

implementation

  function TMyObject.GetMyCalculatedValue: Integer;
  begin
    if FMyCalculatedValue = 0 then
    begin
      FMyCalculatedValue :=
        FObject1.OtherCalculatedValue + // This is also calculated
        FObject2.OtherValue;
    end;

    Result := FMyCalculatedValue;
  end;

end.

用于计算的对象发生变化并且缓存值应该被重置和重新计算的情况并不少见。到目前为止,我们通过使用观察者模式解决了这个问题:对象实现了一个 OnChange 事件,以便其他人可以订阅、在他们更改和重置缓存值时得到通知。这种方法有效,但有一些缺点:

  • 管理订阅需要大量内存。
  • 当缓存值依赖于大量对象(例如列表)时,它不能很好地扩展。
  • 依赖关系不是很具体(即使缓存值仅依赖于一个属性,它也会在其他属性更改时重置)。
  • 管理订阅会影响整体性能并且难以维护(对象被删除、移动……)。
  • 目前尚不清楚如何处理取决于其他计算值的计算。

最后是一个问题:您能否提出其他实现缓存计算值的方法?

4

3 回答 3

4

如果你想避免观察者模式,你可以尝试使用散列方法。

这个想法是你“散列”参数,并检查它是否与保存状态的“散列”匹配。如果没有,那么您重新计算(并因此将新哈希保存为键)。

我知道我让它听起来像是我刚刚想到的,但实际上它被知名软件使用。

例如,SCons(Makefile 替代方案)会检查目标是否需要重新构建,最好采用时间戳方法。

我们已经使用 SCons 一年多了,我们从未发现任何未重建的目标问题,因此他们的哈希运行良好!

于 2009-10-08T08:46:16.117 回答
2

您可以存储所需的外部对象值的本地副本。然后,访问例程将本地副本与外部值进行比较,并且仅对更改进行重新计算。

访问外部对象属性同样会强制对这些属性进行可能的重新评估,因此系统应自动保持自身最新,但仅在需要时重新计算。我不知道您是否需要采取措施避免循环依赖。

这会增加每个对象所需的空间量,但会删除观察者模式。它还将所有计算推迟到需要时,而不是在每次源参数更改时执行计算。我希望这与您的系统相关。

unit Unit1;

interface

type
  TMyObject = class
  private
    FObject1, FObject2: TMyOtherObject;
    FObject1Val, FObject2Val: Integer;
    FMyCalculatedValue: Integer;
      function GetMyCalculatedValue: Integer;
  public
    property MyCalculatedValue: Integer read GetMyCalculatedValue;
  end;

implementation

  function TMyObject.GetMyCalculatedValue: Integer;
  begin
    if (FObject1.OtherCalculatedValue <> FObjectVal1)
    or (FObject2.OtherValue <> FObjectVal2) then
    begin
      FMyCalculatedValue :=
        FObject1.OtherCalculatedValue + // This is also calculated
        FObject2.OtherValue;
      FObjectVal1 := FObject1.OtherCalculatedValue;
      FObjectVal2 := Object2.OtherValue;
    end;

    Result := FMyCalculatedValue;
  end;

end.
于 2009-10-08T10:57:25.370 回答
1

在我的工作中,我使用Bold for Delphi,它可以管理相互依赖的无限复杂的缓存值结构。通常每个变量只包含问题的一小部分。在这个框架中称为派生属性。派生是因为值没有保存在数据库中,它只是依赖于数据库中的其他派生属性或持久属性。

此类属性背后的代码在 Delphi 中作为过程编写或在模型中以 OCL(对象约束语言)编写。如果将其编写为 Delphi 代码,则必须订阅依赖变量。因此,如果属性 C 依赖于 A 和 B,那么每当 A 或 B 更改时,在读取 C 时会自动调用重新计算 C 的代码。所以第一次读取 C 并且读取 B (可能来自数据库)。只要 A 和 B 不变,您就可以读取 C 并获得非常快的性能。对于复杂的计算,这可以节省相当多的 CPU 时间。

不利和坏消息是 Bold 不再受到官方支持,您也无法购买。如果你问足够多的人,我想你可以得到,但我不知道你在哪里可以下载它。在 2005-2006 年左右,它可以从 Borland 免费下载,但现在不再可用。它还没有为 D2009 做好准备,因为有人必须将它移植到 Unicode。

另一种选择是ECO with dot.net from Capable Objects。ECO 是 Visual Studio 中的一个插件。它是一个受支持的框架,与 Delphi 的 Bold 具有相同的想法和作者。许多事情也得到了改进,例如数据绑定用于 GUI 组件。Bold 和 ECO 都使用模型作为具有类、属性和链接的中心点。这些可以保存在数据库或 xml 文件中。使用 ECO 的免费版本,该模型最多可以有 12 个类,但我记得没有其他限制。

粗体和 ECO 包含的不仅仅是派生属性,它们使您更有效率,并允许您思考问题而不是数据库的技术细节或在您的情况下如何缓存值。欢迎您对这些框架提出更多问题!

编辑: 实际上有一个Embarcadero 注册用户的下载链接,用于 D7的Bold for Delphi,相当老了......我知道 D2005 有更新,广告 D2006。

于 2009-10-08T18:11:01.550 回答