如何非常快速地生成具有不同“分支”长度的二维数组

时间:2011-03-29 17:05:42

标签: delphi dynamic-arrays

我是德尔福程序员。 在程序中,我必须生成具有不同“分支”长度的二维数组。 它们非常大,操作需要几秒钟(烦人)。

例如:

var a: array of array of Word;
  i: Integer;

begin
   SetLength(a, 5000000);
   for i := 0 to 4999999 do
      SetLength(a[i], Diff_Values);
end;

我知道命令SetLength(a,dim1,dim2)但不适用。甚至没有为dim2设置最小值(> 0)并且从那里继续,因为dim2的min为0(一些“branches”可以为空)。

那么,有没有办法让它快速?不只是5..10%,而是非常快......

谢谢。

4 个答案:

答案 0 :(得分:5)

在处理大量数据时,必须完成大量工作,这样就可以将理论上的最小值放在可以完成的时间内。

对于每次500万次迭代,您需要:

  • 以某种方式确定“分支”的大小
  • 从内存管理器
  • 分配适当大小的新数组
  • 将新阵列使用的所有内存清零(SetLength自动为您执行此操作)

第1步完全由您控制,可以进行优化。但是,如果您使用现代版本的Delphi,它们的速度和它们的速度差不多。 (如果您使用的是旧版本,则可以从安装FastMM和FastCode中受益,这可以加快这些操作。)

如果合适,您可能会做的另一件事是延迟初始化。不要试图一次分配所有500万个数组,而是先做SetLength(a, 5000000);。然后当你需要进入“分支”时,首先检查它的长度是否为0.如果是,它还没有被初始化,所以将它初始化为适当的长度。这不会节省整体时间,实际上它总共会花费更长的时间,但它确实分散了初始化时间,因此用户没有注意到。

如果你的初始化速度已经达到了它的速度,那么你的情况就是懒惰的初始化不能在这里使用,那么你基本上没有运气了。这是处理大量数据的代价。

答案 1 :(得分:2)

我刚刚使用Diff_Values的常量测试了您的确切代码,并使用GetTickCount()为基本时间计时。如果Diff_Values186则需要1466毫秒,如果Diff_Values187,则Out of Memory失败。您知道,Out of Memory表示Out of Address Space,而不是Out of Memory

在我看来,你分配了大量数据,你用完了RAM而Windows开始分页,这就是为什么它很慢。在我的系统上,我有足够的RAM来为进程分配尽可能多的内容;确实如此,直到它失败。

可能的解决方案

  • 显而易见的一个:不要分配那么多!
  • 找出一种将所有数据分配到一个连续内存块的方法:有助于解决地址空间碎片问题。类似于如何分配“分支”上具有固定大小的二维数组,但如果您的“分支”具有不同的大小,则需要根据您的数据计算不同的数学公式。
  • 查看其他数据结构,可能是缓存在磁盘上的数据结构(以制定2Gb地址空间限制)。

答案 2 :(得分:2)

除了梅森的观点之外,还有一些需要考虑的想法:

如果分支长度在分配后永远不会改变,并且您在所有分支中存储在数组中的项目总数上限,那么您可以通过分配一个巨大的分区来节省一些时间记忆的大块并且自己在那个大块中分裂“分支”。您的数组将成为一维指针数组,该数组中的每个条目都指向该分支的数据的开头。使用单个指针变量跟踪大块中已用空间的“结束”,当需要为新的“分支”保留空间时,将当前的“结束”指针值作为新的开始分支并将“结束”指针增加分支所需的空间量。不要忘记围绕dword边界以避免错位处罚。

这种技术需要更多地使用指针,但是它提供了消除所有堆分配开销的潜力,或者至少用专门构建的非常简单,非常快速的子分配器替换通用堆分配,该子分配符合您的特定用途图案。它的执行速度应该更快,但是需要更多的时间来编写和测试。

此技术还可以避免堆碎片,并减少将所有内存释放到单个释放(而不是在当前模型中的数百万个单独分配)。

要考虑的另一个提示:如果你总是对每个新分配的数组“分支”做的第一件事就是将数据分配到每个槽中,那么你可以在Mason的例子中消除第3步 - 你不需要将其归零记忆如果您要做的就是立即将真实数据分配到其中。这将使你的内存写入操作减少一半。

答案 3 :(得分:1)

假设您可以将整个数据结构放入一个连续的内存块中,您可以一次性完成分配,然后接管索引。

注意:即使您无法将数据放入单个连续的内存块中,您仍然可以通过分配多个大块然后将它们拼接在一起来使用此技术。 / em>的

首先形成一个辅助数组colIndex,它包含每行第一列的索引。将colIndex的长度设置为RowCount+1。您可以通过设置colIndex[0] := 0然后colIndex[i+1] := colIndex[i] + ColCount[i]来构建它。在for循环中执行此操作,该循环运行至RowCount并包括colIndex[RowCount]。因此,在最后一个条目colIndex[RowCount]中,存储元素总数。

现在将a的长度设置为 function GetItem(row, col: Integer): Word; begin Result := a[colIndex[row]+col]; end; 。这可能需要一段时间,但它会比你以前做的更快。

现在你需要编写几个索引器。把它们放在一个班级或记录中。

getter看起来像这样:

row

安装者很明显。您可以内联这些访问方法以提高性能。将它们作为索引属性公开,以方便对象的客户端。

您需要添加一些代码来检查colcolIndex的有效性。您需要使用{$IFOPT R+}作为后者。如果要模拟原始索引的范围检查,可以使用{{1}}使此检查可选。

当然,如果您想在初始实例化后更改任何列数,这是一个完全非首发!