如何解决内存分段并强制FastMM向OS释放内存?

时间:2013-12-18 20:53:50

标签: delphi memory memory-management out-of-memory fastmm

注意:32位应用程序,不计划迁移到64位。

我正在使用一个非常耗费内存的应用程序,并且几乎已经针对内存分配/解除分配优化了所有相关路径。 (没有内存泄漏,没有句柄泄漏,应用程序本身没有任何其他类型的泄漏AFAIK并经过测试。我无法触及的第三方库当然是候选者,但在我的场景中不太可能)

该应用程序将经常分配最多4个单一和单个打包记录的大型单维和二维动态数组。我的意思是5000x5000的记录(单,单,单,单)是正常的。在给定时间内甚至还有6或7个这样的阵列工作。这是必需的,因为在这些阵列上进行了大量的交叉计算,并且从磁盘读取它们将是真正的性能杀手。

有了这个澄清之后,由于这些大型动态数组在释放它们后不会消失,我会失去很多内存错误,无论我将它们设置为0还是最终确定它们。这当然是FastMM为了快速而做的事情,我知道的很多。

我正在使用以下方法跟踪FastMM分配的块和处理消耗的内存(RAM + PF):

function CurrentProcessMemory(AWaitForConsistentRead:boolean): Cardinal;
var
  MemCounters: TProcessMemoryCounters;
  LastRead:Cardinal;
  maxCnt:integer;
begin
  result := 0;// stupid D2010 compiler warning
  maxCnt := 0;
  repeat
    Inc(maxCnt);
    // this is a stabilization loop;
    // in tight loops, the system doesn't get
    // much chance to release allocated resources, which in turn will get falsely
    // reported by this function as still being used, resulting in a false-positive
    // memory leak report in the application.
    // so we do a tight loop here, waiting, until the application reported memory
    // gets stable.
    LastRead := result;
    MemCounters.cb := SizeOf(MemCounters);
    if GetProcessMemoryInfo(GetCurrentProcess,
        @MemCounters,
        SizeOf(MemCounters)) then
      Result := MemCounters.WorkingSetSize + MemCounters.PagefileUsage
    else
      RaiseLastOSError;
    if AWaitForConsistentRead and (LastRead <> 0) and (abs(LastRead - result)>1024) then
    begin
      sleep(60);
      application.processmessages;
    end;
  until (not AWaitForConsistentRead) or (abs(LastRead - result)<1024) or (maxCnt>1000);
  // 60 seconds wait is a bit too much
  // so if the system is that "unstable", let's just forget it.
end;

function CurrentFastMMMemory:Cardinal;
var mem:TMemoryManagerUsageSummary;
begin
  GetMemoryManagerUsageSummary(mem);
  result := mem.AllocatedBytes + mem.OverheadBytes;
end;

我在64位计算机上运行代码,崩溃前的最高内存消耗量约为3.3 - 3.4 GB。之后,我在应用程序的任何地方都会遇到与内存/资源相关的崩溃问题。花了我一些时间把它归结为大型动态阵列的使用,这些使用被埋没在第三方库中。

我越过这一点的方式是我通过重新启动自己并使用某些参数关闭,使应用程序从它停止的地方恢复。 如果内存消耗公平且当前操作完成,这一切都很好用。

当前内存使用量为1GB且下一个要处理的操作需要处理2.5 GB或更多内存时,会出现一个大问题。我的当前代码在恢复之前将自身限制为1.5 GB内存的上限,但在这种情况下,我必须将限制降低到1 GB以下,这基本上会使应用程序在每次操作后自行恢复,甚至不保证一切都会好的。

如果另一个操作有更大的数据集需要处理,并且需要总共4GB或更多内存,该怎么办?

要注意的是我并不是在讨论内存中实际的4 GB,而是通过分配巨大的动态数组来消耗内存,一旦取消分配操作系统就不会回来,因此它仍然将其视为已消耗,因此它会增加

因此,我的下一个攻击点是强制fastmm释放所有(或至少部分)内存到操作系统。我专门针对这里庞大的动态数组。同样,这些都在第三方库中,所以重新编码并不是真正的顶级选项。修改fastmm代码并编写proc来释放内存会更加容易和快捷。

我无法从FastMM切换为当前整个应用程序,并且一些第三方库在使用PushAllocationGroup时进行了大量编码,以便快速查找并查明任何内存泄漏。我知道我可以编写一个虚拟的FastMM单元来解决编译参考,但是如果没有这种快速和确定的泄漏检测,我将会离开。

总之:有什么方法可以强制FastMM向操作系统发布至少一些大块? (当然,确实存在,实际的问题是:是否有人写过它,如果是的话,分享心灵?)

由于

稍后编辑:

我很快就会提出一个小型的相关测试应用程序。模拟一个

似乎不那么容易

3 个答案:

答案 0 :(得分:3)

我怀疑这个问题实际上是关于FastMM的。对于巨大的内存块,FastMM不会进行任何子分配。您的分配请求将通过直接VirtualAlloc处理。然后解除分配是VirtualFree

假设您在一个连续的块中分配这些380MB对象。我怀疑你实际拥有的是粗糙的2D动态数组。而且它们不是单一的分配。一个5000x5000参差不齐的2D动态阵列需要5001个分配才能初始化。一个用于行指针,5000用于行。那些将是中等FastMM块。会有分配。

我觉得你问的太多了。根据我的经验,任何时候你需要32位进程超过3GB的内存,它的游戏结束。在内存不足之前,地址空间的碎片将阻止您。你不能希望这个工作。切换到64位,或使用更聪明,要求更低的分配模式。或者您真的需要密集的2D阵列吗?你可以使用稀疏存储吗?

如果无法通过这种方式减轻内存需求,可以使用内存映射文件。这将允许您使用64位系统具有的额外内存。系统的磁盘缓存可能大于4GB,因此您的应用程序可以遍历超过4GB的内存而无需实际需要访问磁盘。

你当然可以尝试不同的内存管理器。老实说,我不抱任何希望它会有所帮助。您可以编写一个使用HeapAlloc的简单替换内存管理器。并启用低碎片堆(默认情况下从Vista启用)。但我真诚地怀疑它会有所帮助。我担心你不会有快速解决方案。要解决此问题,您需要对代码进行更基本的修改。

答案 1 :(得分:1)

正如其他人所说的那样,你的问题最有可能归因于内存碎片化。您可以使用VirtualQuery来测试这一点,以创建如何为应用程序分配内存的图片。您很可能会发现虽然新阵列的总内存可能已足够,但您没有足够的连续内存。

FastMem已经做了很多尝试,以避免因内存碎片问题。 “小”分配在地址空间的低端完成,而“大”分配在高端完成。这避免了一个常见问题,即一系列大而小的分配随后释放所有大分配会导致大量碎片存储器几乎无法使用。 (当然,任何比原始大量分配略大的东西都无法使用。)

要了解FastMem方法的优点,请想象您的记忆如下:

每个数字代表一个100mb的块 [0123456789012345678901234567890123456789]

以“s”表示的小额分配 大写字母重新分配大量分配 [0sssss678901GGGGFFFFEEEEDDDDCCCCBBBBAAAA]

现在如果你释放所有大块,你应该可以在以后执行类似的大量分配时遇到麻烦 [0sssss6789012345678901234567890123456789]

问题是“大”和“小”是相对的,并且高度依赖于应用程序的性质。 FastMem定义了“大”和“小”之间的分界线。如果您碰巧有一些FastMem会将其分类为大的小分配,您可能会遇到以下问题。

[0sss4sGGGGsFFFFsEEEEsDDDDsCCCCsBBBBsAAAA]

现在如果你释放了你剩下的大块:
[0sss4s6789s1234s6789s1234s6789s1234s6789]

尝试分配大于400mb的东西会失败。


选项

  1. 您可以调整FastMem设置,以便FastMem认为所有“小”分配都很小。但是,在某些情况下这不起作用:
    • 您使用的任何为您的应用程序分配内存但绕过FastMem的DLL仍可能导致碎片。
    • 如果你没有将你的大块全部一起释放,剩下的那些可能会导致碎片化,随着时间的推移会逐渐变得更糟。
  2. 您可以自己承担内存管理的任务。
    • 分配一个非常大的块,例如在应用程序的整个生命周期内保留3.5GB。
    • 您可以确定在设置新阵列时使用的指针位置,而不是使用动态数组。
  3. 当然最简单的选择是64位。
  4. 您可以考虑替代数据结构。
    • 确实需要阵列查找功能吗?如果没有,另一个以较小块分配的结构就足够了。
    • 即使您确实需要数组查找,也请考虑分页数组。稀疏数组是数组和链表的组合。数据存储在页面上,链接列表链接每个页面。
    • 一个简单的变体(因为你提到你的数组是二维的)将利用它:一个维度形成自己的数组,为第二维提供查找到多个数组之一。
  5. 与备用数据结构选项相关,请考虑在磁盘上存储一些数据。是的表现会慢一点。但是如果能够找到一种有效的缓存机制,那么可能就不那么多了。最好慢一点,但不要崩溃。

答案 2 :(得分:0)

动态数组是在Delphi中引用计数的,因此它们应该在不再使用时自动释放。 与字符串一样,它们在共享/存储在多个变量/对象中时使用COW(写入时复制)进行处理。所以看起来你有某种内存/引用泄漏(例如,内存中的一个对象仍然是对数组的引用)。 只是为了确定:你没有做任何低级指针技巧,不是吗?

所以请一定要发布一个测试程序(或通过电子邮件发送完整的程序),以便我们其中一个人可以查看它。