【问题标题】:Find largest free memory block找到最大的空闲内存块
【发布时间】:2010-02-15 10:40:38
【问题描述】:

有时会出现碎片化时内存不足的问题。

是否有可能找到最大的空闲内存块? 我将 Delphi 2007 与 FastMM 一起使用。在 Windows XP 上开发,在 Windows 2003 上运行应用程序。

问候

编辑: 我可以添加应用程序在 Windows Server 2003 x64 上具有 32 GB 内存的服务器上运行的信息。但该应用程序是 32 位应用程序,因此理论上每个实例的最大分配内存为 2 GB。许多实例同时运行。我不认为是总的物理内存太少了。我猜当应用程序启动时有一个 32 位的虚拟内存空间。这可能会在运行时过于分散。

我还发现了 FastGetHeapStatus 方法,它返回一个 THeapStatus,其中包含一些用于释放内存的字段。也许我可以使用这些。

EDIT2: 我找到了这个How to get the largest available continues memory block。 代码是 C 但也许可以翻译成 Delphi。

【问题讨论】:

  • 小修正:在 64 位操作系统版本上,您可以获得 32 位进程的完整 4 GB 地址范围。该进程可能无法使用它(由于内存管理例程中使用了带符号的数据类型),但该限制与 32 位操作系统不同。 AFAIR Delphi 确实有问题。
  • 是的,delphi 在 RTL 中到处使用大量有符号值

标签: delphi memory-management


【解决方案1】:

不,这是旧 Turbo Pascal 中的“maxavail”,这是一个经常被请求的功能,但不幸的是,在多用户、多任务环境中它是一个无用的概念

堆管理器可以知道它自己维护的内存中的最大块,但这通常很小,因为大块直接从窗口分配(并返回到)。

并且逐步尝试分配更大块的方案将失败,因为操作系统将授予请求,即使这意味着交换到磁盘(您不想要)。尝试使用 windows api 调用挖掘此类值的技巧也是如此。

整个保护模式环境以共享内存为基础,每个应用程序只使用所需的内存。忽略这一点并假装一切都像在 Dos 下一样,只会引起同时运行多个应用程序的人的大量抱怨。

如果您的应用程序确实依赖于此,请将其设置为具有安全(小)默认值的配置设置(启动时为某事分配多少内存)。如果它真的很重要,请在设置过程中与用户面对面

当然,人们总是可以通过执行一些 winapi 调用并假设没有其他应用程序运行来通过启发式尝试播种默认值。但始终将最终决定权留给用户,特别是对于服务器应用程序。

【讨论】:

    【解决方案2】:

    这是您想要的 Delphi 代码翻译:

    function GetLargestFreeMemRegion(var AAddressOfLargest: pointer): LongWord;
    var
      Si: TSystemInfo;
      P, dwRet: LongWord;
      Mbi: TMemoryBasicInformation;
    begin
      Result := 0;
      AAddressOfLargest := nil;
      GetSystemInfo(Si);
      P := 0;
      while P < LongWord(Si.lpMaximumApplicationAddress) do begin
        dwRet := VirtualQuery(pointer(P), Mbi, SizeOf(Mbi));
        if (dwRet > 0) and (Mbi.State and MEM_FREE <> 0) then begin
          if Result < Mbi.RegionSize then begin
            Result := Mbi.RegionSize;
            AAddressOfLargest := Mbi.BaseAddress;
          end;
          Inc(P, Mbi.RegionSize);
        end else
          Inc(P, Si.dwPageSize);
      end;
    end;
    

    你可以这样使用它:

    procedure TForm1.FormCreate(Sender: TObject);
    var
      BaseAddr: pointer;
      MemSize: LongWord;
    begin
      MemSize := GetLargestFreeMemRegion(BaseAddr);
      // allocate dynamic array of this size
      SetLength(fArrayOfBytes, MemSize - 16);
    
      Caption := Format('Largest address block: %u at %p; dynamic array at %p',
        [MemSize, BaseAddr, pointer(@fArrayOfBytes[0])]);
    end;
    

    请注意,我必须从最大大小中减去 16 个字节,大概是因为动态数组本身使用了从同一块内存分配的几个字节,所以下一次分配是基于 16 的下一个倍数。

    【讨论】:

      【解决方案3】:

      在虚拟内存系统上,虚拟地址空间意味着虚拟页面可以映射到任何地方。您不需要大的连续物理内存块。如果您在虚拟地址空间碎片方面遇到问题,那么您可能需要不同的内存管理策略。

      但是,大多数选项都需要您的应用程序代码在一定程度上了解内存管理策略。我不相信这个问题有一个快速的解决方案——你可能需要接受相当大的手术来解决它。这些选项都不容易实现,您必须找到最有可能在您的特定情况下工作的选项。

      我可以看到的主要选项是:自定义内存分配器、涉及 AWE(见下文)或在应用程序中重建内存分配策略。

      选项 1:自定义内存分配器

      自定义内存分配器在 C 和 C++ 圈子中并不少见。您也许可以实现类似的东西。您有两种可能性:

      • 使用一种机制构建内存分配器,该机制尝试将相邻的空闲块合并为一个更大的块(您可以将其作为尝试从失败的内存分配中恢复的一部分运行)。这可能允许您透明地管理内存,而无需应用程序知道。实现这一点很繁琐且技术性很强,但可能是可行的。

        这种方法的主要好处是它是唯一不需要您更改现有应用程序代码的方法。缺点是它不能保证工作;合并操作仍有可能无法合并足够大的内存块来满足请求。合并操作还可能在运行时导致应用程序响应严重暂停。

      • 您可能需要以允许压缩数据结构的方式构建应用程序。这将要求您维护支持被移动对象的句柄,即双重间接机制。我猜测可能有一种或相当少的不同数据结构会导致这种碎片问题,因此可能可以在您的应用程序中本地化任何重新架构工作。

      选项 2:PAE

      Windows 确实支持直接操作 MMU 的工具,并且有几种可能适用于您的应用程序。这肯定需要您的应用程序提供明确的架构支持,但提供了使用远大于 2GB 的内存池的可能性。

      在 Windows 的服务器版本上,查看 API's 支持的 PAE,它允许您手动操作系统的 MMU 并重新映射内存块。这可能通过以下两种方式之一对您有所帮助

      • 您可以为数据结构构建管理器,将这种机制用作管理数据的固有部分。

      • 如果您可以将数据结构中的项目调整到页面边界,则可以将其用作整合内存的一种方式。

      但是,这种方法需要您重新设计应用程序,以便对象引用有足够的信息来管理显式换入过程(可能是某种覆盖管理器,具有通过该系统引用的对象的代理机制)。这意味着任何涉及 PAE 的解决方案都不能替代 FastMM - 您必须修改应用程序以明确支持 PAE。

      但是,这种代理机制可能意味着该子系统对客户端可能是相对透明的。除了管理间接和覆盖的开销(这可能是也可能不是一个重要问题)之外,代理可能与原始 API 几乎无法区分。这种类型的方法最适用于数量相对较少且互连最少的大型重量级对象 - 典型的应用程序是磁盘缓存。代理必须保留在内存中的固定位置,通过覆盖机制访问较大的对象。 YMMV。

      选项 3:从源头解决问题

      一种可能性是您的对象分配策略可以在应用程序代码中进行优化(可能从批量分配的对象池中,然后在应用程序中进行管理)。这可能允许您从应用程序中处理内存碎片,而无需尝试重​​新编写内存管理器。

      同样,这种方法意味着您必须重新构建应用程序的某些部分,并且该方法的适用性实际上取决于应用程序的性质。只有您才能判断这可能会奏效。

      【讨论】:

      • 1) 如果块是不可移动的,则合并相邻块并不会真正产生太多收益。自己的 memmanager 可能是一个完全不同的解决方案,但您可能必须向管理器添加有关应用程序访问模式的知识,才能比 fastmm 做得更好。 2) PAE 没有帮助,因为 Delphi 编译器是 32,而不是 64 位,并假定 32 位线性指针。像不存储低 4 位这样的方案会破坏指针运算。将内存密集位分解为 64 位 FPC dll 是一种更明智的解决方案。我会选择选项 3,并结合分配启动时所需的最大块
      • 感谢您的回答。我敢肯定,我们不会建立自己的内存管理器。我们真正希望的是 64 位 Delphi 编译器,因为现在内存很便宜。与此同时,我在应用程序中添加了一个内存计,以便用户可以观察情况。如果出现 EOutOfMemory,我建议重新启动... :)
      猜你喜欢
      • 2017-12-01
      • 1970-01-01
      • 2011-09-05
      • 2011-02-01
      • 1970-01-01
      • 2018-08-21
      • 1970-01-01
      • 2016-10-05
      • 2013-12-29
      相关资源
      最近更新 更多