【问题标题】:Win32 HeapCreate() initialSize doesn't support big allocationsWin32 HeapCreate() initialSize 不支持大分配
【发布时间】:2014-05-06 17:30:18
【问题描述】:

我们在使用 HeapCreate()/HeapAlloc() 进行大分配时遇到问题 (> 512K)

我们正在开发一个 C++ 服务器应用程序,同时对一些图像执行一些“图像处理”操作。它应该可以工作很长时间而无需重新启动。

我们的处理模型非常具体。 服务器启动,执行一些必要的分析以检测最大值。给定硬件配置的并发图像数量,这意味着以最佳性能稳定工作,快速达到最大负载,然后在大多数时间或多或少地以相同的高负载工作,具体取决于输入队列。

这意味着我们在开始时使用了所有需要的内存,并且内存总量不应该增加(如果一切正常的话)。 我们的痛苦是分裂。传入图像的大小可能从 400K 到可能的 50M 不等,并且每个图像的处理导致相应的(与图像大小成比例)相对较大的 OPENCV 分配。处理场景(和相关分配)会有所不同,取决于图像细节,分配/释放操作非常密集,然后在一段时间后我们会得到碎片。鉴于微不足道的改进,开发了一些局部优化。 实际上,大约在大约之后,我们会产生内存不足/碎片相关的影响。 50000-70000 张图片,这是不可接受的。当前的解决方案是重新启动服务器,这远非理想。

解决问题的最初天真的建议是:

  • 我们有自己的自定义堆,最初提交整个所需的内存。
  • 所有必需的“大”OPENCV 分配(并且只有那些)重定向到此堆
  • 此时,碎片到达,我们停止新输入并完成所有正在运行的作业。
  • 这意味着所有与图像相关的分配都已释放。
  • 检查堆并在需要时清理它(例如由于内存泄漏)
  • 现在,我们的堆完全是空的,可以从头开始。再次打开输入。

简单的概念验证项目很快就发现了以下内容:

  • HeapCreate(),最初提交 250M,每次我从中调用 HeapAlloc() 时增长 10M!很奇怪,不是吗?
  • 正如使用 HeapWalk() 所识别的那样,提交的内存不是保留在一个连续的块中,而是保留为超过 500 个块的列表,每个块 512K。所以它们都不适合我的 10M 请求和调用堆来处理未提交的内存

似乎 Win32 自定义堆仅针对小型分配进行了优化,我无法找到一种方法来满足我的需要 :( VirtualAlloc() 似乎是一个解决方案,但它是非常底层的 API,使用它意味着开发我自己的内存管理系统,似乎是某种轮子改造。

我想相信存在一些标准方法,但我找不到它。 任何帮助或相关资源阅读将不胜感激

【问题讨论】:

  • 抱歉初始格式。这是一个技术问题。
  • 这个问题在今天根本无法解决。重新构建程序以针对 x64。
  • @HansPassant:重新定位到 64 位模型可能只是推迟问题而不是解决问题。如果一个程序有一个有害的碎片问题并且它必须无限期地运行,它最终会耗尽虚拟地址空间并且它可能会耗尽可用的存储空间(如果碎片严重到无法释放页面)。即使延期很长时间,业绩也可能会出现流失。
  • 不,分割一个 256 TB 的地址空间,却找不到一个 50 MB 的漏洞,代码在 2 GB 的空间中工作是完全不可能的。
  • @Hans Passant:Windows x64 的用户模式虚拟地址空间为 8 TB,而不是 256。由于碎片问题,该应用程序无法在 2 GB 空间中无限期地运行;因此问题。如果碎片严重到足以防止内存未提交,您最终将达到提交限制(通常约为 1 TB)。

标签: c++ windows memory-management heap-memory fragmentation


【解决方案1】:

一些想法:

  1. 堆通常管理来自较大内存块的小子分配。如果您需要大量分配,堆可能不是解决方案。你可能不得不自己动手,直接处理虚拟内存。

  2. 尚不清楚用于大分配的 HeapAlloc 是否实际上是从堆的保留内存中分配的。 MSDN 有点含糊,有时自相矛盾,但low-fragmentation heap (LFH) 上的页面说大于 16 KB 的分配不使用 LFH。这可能意味着堆会为您跟踪它,但真正满足来自 VirtualAlloc 调用而不是来自保留内存的大量分配。如果是这样的话,使用堆可能只会让事情变得更糟。 (无论如何,无论是否启用 LFH,都值得一试。)

  3. 如果您的问题往往是碎片而不是实际的内存耗尽,那么您最好还是浪费一些内存来消除碎片。如果您的最大分配需要 50 MB,那么您可能会考虑将所有分配都设置为 50 MB,即使图像非常小。平均而言,您将拥有更少的分配块(因此您不能一次处理尽可能多的图像),但如果分配总是相同的大小,您将永远不会出现碎片。这是否是一个可以接受的权衡取决于您的具体情况。如果它们更常见,您可以妥协并拥有一堆大小为 X 的块来处理较小的块,而只有几个大小为 Y 的块来处理尽可能大的块。

  4. 另一种方法是平铺,尽管这会极大地影响您的应用程序的架构方式。这个想法是使用固定大小的图块而不是可变大小的图像。根据图块大小,将图像切割成尽可能多的图块。瓦片是独立处理的,输出图像是由瓦片重新组合而成的。由于所有图块的大小相同,因此可以避免碎片。一些图像处理非常适合这一点,但其他类型则不然。

【讨论】:

  • 谢谢,瓷砖是个好主意。不幸的是,我们必须分析“整个”文档区域,至少在第一阶段。通常说,50M 是较大的 800M 块的小子分配。以这种方式配置堆将是理想的解决方案。是的,看来我们必须处理 VirtualAlloc()。我只是希望这种面向大分配的堆存在一些标准或已知的实现(绝对基于 VirtualAlloc())
  • 您可以在平铺之上构建一个抽象,这样,从代码的角度来看,它只是一个大图像,但表示被分成大小相等的平铺。跨度>
猜你喜欢
  • 1970-01-01
  • 2011-05-10
  • 2013-02-04
  • 2017-04-17
  • 2013-06-28
  • 1970-01-01
  • 2019-04-04
  • 1970-01-01
  • 2022-11-23
相关资源
最近更新 更多