如何避免堆碎片?

Posted

技术标签:

【中文标题】如何避免堆碎片?【英文标题】:How to avoid heap fragmentation? 【发布时间】:2010-09-14 03:00:48 【问题描述】:

我目前正在处理一个需要大量内存的医学图像处理项目。我可以做些什么来避免堆碎片并加快对已加载到内存中的图像数据的访问?

该应用程序是用 C++ 编写的,可以在 Windows XP 上运行。

编辑: 应用程序对图像数据进行一些预处理,例如重新格式化、计算查找表、提取感兴趣的子图像......在处理过程中应用程序需要大约 2 GB RAM,其中大约 1.5 GB 可用于图像数据。

【问题讨论】:

您可以编写自己的自定义分配器,但这很难 我赌的是 C++ - 但这是真的:只要不知道潜在的“范式”,这个问题就无法回答...... 这个问题需要澄清一些。什么是“巨大”的内存量?什么语言(如前所述)。您担心碎片化在哪里——在已经加载的图像数据中,还是因为必须操作数据而担心? 这个问题已经在here讨论过。也许你可以在那里找到一些有用的信息。 【参考方案1】:

如果没有关于问题的更多信息(例如语言),您可以做的一件事是通过重用分配而不是分配、操作和释放来避免分配混乱。 dlmalloc 等分配器比 Win32 堆更好地处理碎片。

【讨论】:

【参考方案2】:

在这里猜测您的意思是避免碎片化而不是避免碎片整理。还猜测您正在使用非托管语言(可能是 c 或 C++)。我建议您分配大块内存,然后从分配的内存块中提供堆分配。这个内存池因为包含大块内存,所以不太容易产生碎片。总而言之,您应该实现一个自定义内存分配器。

查看有关此here 的一些一般性想法。

【讨论】:

【参考方案3】:

我猜你正在使用非托管的东西,因为在托管平台中,系统(垃圾收集器)负责处理碎片。

对于 C/C++,您可以使用其他分配器,而不是默认分配器。 (stackowerflow 上已经有一些关于分配器的线程)。

此外,您还可以创建自己的数据存储。例如,在我目前正在进行的项目中,我们有一个用于位图的自定义存储(池)(我们将它们存储在一大块连续的内存中),因为我们有很多,并且我们跟踪堆碎片过大时进行碎片整理。

【讨论】:

分片独立于垃圾回收。当长寿命对象分散在整个堆中时会发生这种情况,因为它们的分配与短寿命对象的分配混合在一起。短暂的东西如何被释放并不重要。 好的垃圾收集器会通过移动对象和更新引用来处理碎片。 我正要反对这些行或那些行,但我让步了。允许您还拥有由 GC-plus-plus 介导的所有内存访问,这可以。我有没有告诉过你步行三英里去学校...【参考方案4】:

您可能需要实施手动内存管理。图像数据寿命长吗?如果没有,那么您可以使用 apache Web 服务器使用的模式:分配大量内存并将它们包装到内存池中。将这些池作为函数中的最后一个参数传递,以便它们可以使用池来满足分配临时内存的需要。一旦调用链完成,池中的所有内存都应该不再使用,因此您可以清理内存区域并再次使用它。分配很快,因为它们只意味着向指针添加一个值。释放非常快,因为您将一次释放非常大的内存块。

如果您的应用程序是多线程的,您可能需要将池存储在线程本地存储中,以避免跨线程通信开销。

【讨论】:

【参考方案5】:

如果您正在进行医学图像处理,您可能一次分配大块(512x512,每像素 2 字节图像)。如果您在图像缓冲区的分配之间分配较小的对象,那么碎片会咬你。

对于这个特定的用例,编写自定义分配器并不一定很难。您可以为 Image 对象使用标准 C++ 分配器,但对于像素缓冲区,您可以使用全部在 Image 对象中管理的自定义分配。这是一个快速而肮脏的大纲:

使用结构的静态数组,每个结构都有: 可以容纳 N 个图像的可靠内存块 - 分块将有助于控制碎片 - 尝试初始 N 为 5 左右 一个并行的布尔数组,指示相应的图像是否正在使用中 要分配,请在数组中搜索空缓冲区并设置其标志 如果没有找到,则在数组末尾追加一个新结构 要解除分配,请在数组中找到相应的缓冲区并清除布尔标志

这只是一个简单的想法,有很大的变化空间。主要技巧是避免释放和重新分配图像像素缓冲区。

【讨论】:

嗨,Jeff,您从事过医学图像处理工作吗? Off and on :) 您可能想查看 vtk 甚至 osirix 以获取参考资料...实际上,上面的想法只是在 C++ 中处理统一大小的自定义分配的一种标准方法虽然...【参考方案6】:

有答案,但不知道问题的细节很难笼统。

我假设是 32 位 Windows XP。

尽量避免需要 100 MB 的连续内存,如果你运气不好,一些随机 dll 会通过你的可用地址空间在不方便的点加载自己,从而迅速减少非常大的连续内存区域。根据您需要的 API,这可能很难防止。除了一些“正常”内存使用之外,仅分配几个 400MB 的内存块会导致您无处可分配最终的“小”40MB 块,这可能会非常令人惊讶。

另一方面,每次预分配合理大小的块。大约 10MB 左右是一个很好的折衷块大小。如果您能够设法将数据划分为这种大小的块,您将能够合理有效地填充地址空间。

如果您仍然会用完地址空间,您将需要能够根据某种缓存算法将块分页进出。选择正确的块来分页在很大程度上取决于您的处理算法,并且需要仔细分析。

选择将内容分页到哪里是另一个决定。您可能决定只将它们写入临时文件。您还可以调查 Microsoft 的地址窗口扩展 API。在任何一种情况下,您都需要在应用程序设计中小心清理所有指向即将被分页的东西的指针,否则会发生非常糟糕的事情(tm)。

祝你好运!

【讨论】:

【参考方案7】:

如果您要对大型图像矩阵执行操作,您可能需要考虑一种称为“平铺”的技术。这个想法通常是将图像加载到内存中,以便相同的连续字节块不会在一行中包含像素,而是在 2D 空间中包含一个正方形。这背后的基本原理是您将在 2D 中而不是在一条扫描线上执行更多彼此更接近的操作。

这不会减少您的内存使用,但可能会对页面交换和性能产生巨大影响。

【讨论】:

【参考方案8】:

您将在这里遇到的是虚拟地址范围限制,在 32b 的 Windows 中最多为您提供 2 GB。您还应该知道,使用 DirectX 或 OpenGL 等图形 API 将使用这 2 GB 的大部分用于帧缓冲区、纹理和类似数据。

32b 应用程序的 1.5-2 GB 很难实现。最优雅的方法是使用 64b OS 和 64b 应用程序。即使使用 64b 操作系统和 32b 应用程序,只要您使用 LARGE_ADDRESS_AWARE,这也可能是可行的。

但是,由于您需要存储图像数据,您也可以使用 File Mapping as a memory store 来解决此问题 - 这可以通过提交和访问内存的方式来完成,但不使用任何虚拟地址。

【讨论】:

【参考方案9】:

如果您可以准确地隔离那些您可能分配大块的地方,您可以(在 Windows 上)直接调用 VirtualAlloc 而不是通过内存管理器。这将避免在普通内存管理器中产生碎片。

这是一个简单的解决方案,不需要您使用自定义内存管理器。

【讨论】:

以上是关于如何避免堆碎片?的主要内容,如果未能解决你的问题,请参考以下文章

面试题:Linux是如何避免内存碎片的

有没有办法处理 AVR/Arduino 微控制器中的堆内存碎片?

sqlserver索引碎片怎么避免

在 Java 中分配大量数组时避免内存碎片

伙伴系统之避免碎片--Linux内存管理(十六)

在 C# 中进行大量、快速和频繁的内存分配期间避免 OutOfMemoryException