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

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

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

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

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

澄清这一点后,我经常遇到内存不足错误,因为这些大型动态数组在释放它们后不会消失,无论我将它们设置为 0 还是最终确定它们。这当然是FastMM为了快而做的事情,我就知道这么多。

我使用以下方法跟踪 FastMM 分配的 block 和进程消耗的内存 (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 代码并编写一个过程来释放内存会更容易、更快捷。

我无法从 FastMM 切换,因为目前整个应用程序和一些第 3 方库都围绕 PushAllocationGroup 的使用进行了大量编码,以便快速查找并查明任何内存泄漏。我知道我可以编写一个虚拟的 FastMM 单元来解决编译引用问题,但我将无法进行这种快速且确定的泄漏检测。

总结:有什么方法可以强制 FastMM 至少将一些大块释放到操作系统吗? (嗯,当然有,实际的问题是:有人写过它吗?如果是的话,介意分享吗?)

谢谢

稍后编辑:

我很快就会想出一个小型的相关测试应用程序。模拟一个似乎并不那么容易

最佳答案

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

假设您在一个连续的 block 中分配这些 380MB 的对象。我怀疑你实际上拥有的是不规则的二维动态数组。而且它们不是单一分配。 5000x5000 不规则的二维动态数组需要 5001 次分配来初始化。 1 个用于行指针,5000 个用于行。这些将是中等 FastMM block 。会有二次分配。

我觉得你的要求太多了。根据我的经验,只要 32 位进程需要超过 3GB 的内存,游戏就结束了。地址空间碎片会在内存耗尽之前阻止您。你不能指望这能起作用。切换到 64 位,或者使用更聪明、要求更低的分配模式。或者你真的需要密集的二维阵列吗?可以使用稀疏存储吗?

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

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

关于delphi - 如何解决内存分段并强制FastMM释放内存给OS?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20668182/

相关文章:

c - 我是否以错误的方式使用了 realloc?

C# - 取消绑定(bind)结构 "destruction"上的事件

javascript - JavaScript 闭包内存效率高吗?

c++ - 如何在 C++ 中跟踪内存使用情况

c++ - 内联汇编语言

delphi - 如何让 PNG 在 D2009 中工作?

android - 如何使我的 Delphi 代码与多个平台兼容?

java - Delphi SetBit-函数和TBitRange-Java中的类型

delphi - 为什么 Detours lib 不适用于虚拟方法?

c++ - 在 Qt Creator 中使用 'Analyze Memory' 工具