如何避免堆内存碎片化?

13

我目前正在从事一项医学图像处理项目,需要大量的内存。有什么方法可以避免堆碎片和加速已加载到内存中的图像数据的访问速度?

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

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


你可以编写自己的自定义分配器,但这很困难 - Adam Rosenfield
我的赌注在C++上 - 但这是真的:只要底层的“范式”未知,问题就无法回答...。 - Georgi
这个问题需要更明确的表述。什么是“巨大”的内存?使用哪种编程语言(如之前所述)?你担心的是已经加载的图像数据中的碎片,还是因为你需要操作数据而感到担忧? - Mark
这个问题已经在这里讨论过了。也许你可以在那里找到一些有用的信息。 - Mark
9个回答

19
如果您进行医学图像处理,那么很可能会一次性分配大块内存(512x512,2字节每像素的图像)。如果您在图像缓冲区分配之间分配较小的对象,则碎片化将使您疲惫不堪。
编写自定义分配器对于这种特定用例并不难。您可以为Image对象使用标准C++分配器,但是对于像素缓冲区,您可以使用完全在Image对象内部管理的自定义分配。以下是一个简要粗略的概述:
- 使用结构体的静态数组,每个结构体具有:
- 可以容纳N个图像的连续内存块 -- 分块将有助于控制碎片化 -- 尝试初始N值为5或更多 - 并行布尔数组,指示相应的图像是否正在使用。
- 要进行分配,请搜索空缓冲区并设置其标志
- 如果没有找到,则将新结构追加到数组末尾
- 要进行解除分配,请在数组中查找相应的缓冲区并清除布尔标志。
这只是一个简单的想法,可以有很多变化空间。主要诀窍是避免释放和重新分配图像像素缓冲区。

嗨,杰夫,你是否曾经从事过医学图像处理方面的工作? - Thomas Koschel
1
关了再开 :) 你可能想查阅vtk甚至osirix获取参考资料...实际上,上述想法只是在C++中处理统一大小的自定义分配的标准方法... - Jeff Kotula

6
有答案,但不知道问题的具体细节很难做出通用性建议。
我假设你使用32位的Windows XP。
尽量避免需要数百MB连续内存,如果你不幸运,一些随机的dll将在可用地址空间的不方便位置加载自己,迅速减少非常大的连续内存区域。根据你需要的API,这可能很难防止。令人惊讶的是,只分配几个400MB的内存块以及一些“正常”的内存使用就可以让你没有地方分配最后的“小”40MB块。
另一方面,预先分配合理大小的块。大约10MB左右的块大小是一个很好的折衷块大小。如果你能够将数据划分成这种大小的块,你就能够比较高效地填充地址空间。
如果你仍然会用完地址空间,你需要能够基于某种缓存算法对块进行页面调入和调出。选择要调出的正确块将在很大程度上取决于你的处理算法,并需要进行仔细的分析。
选择将事物调出到何处是另一个决定。你可以决定将它们只写入临时文件。你也可以调查Microsoft的Address Windowing Extensions API。在任何一种情况下,你需要在应用程序设计中小心清理任何指向即将被调出的东西的指针,否则会发生非常糟糕的事情。
祝你好运!

5
如果您要在大型图像矩阵上执行操作,您可能需要考虑一种称为“平铺”的技术。通常的想法是将图像加载到内存中,以便相同的连续字节块不包含一行中的像素,而是2D空间中的一个正方形。其背后的原理是,您会执行更多接近于2D空间的操作,而不是在一个扫描线上进行操作。
这不会减少您的内存使用,但可能会对页面交换和性能产生巨大影响。

2

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


2
你将会碰到的问题是虚拟地址范围限制,在32位Windows中最多只能使用2GB。你还应该注意,使用图形API如DirectX或OpenGL将使用大量的这2GB用于帧缓冲、纹理和类似数据。
对于32位应用程序来说,1.5-2GB是相当难以实现的。最优雅的方法是使用64位操作系统和64位应用程序。即使是在64位操作系统和32位应用程序下,只要使用LARGE_ADDRESS_AWARE,这也可能是可行的。
然而,由于需要存储图像数据,您还可以通过使用文件映射作为内存存储绕过这个问题 - 这可以这样做,即您有一个已提交并可访问的内存,但根本不使用任何虚拟地址。

1
猜测您的意思是避免碎片化而不是避免碎片整理,并且猜测您正在使用非托管语言(可能是C或C ++)。我建议您分配大块内存,然后从分配的内存块中提供堆分配。由于此内存池包含大块内存,因此不太容易出现碎片化。总之,您应该实现自定义内存分配器。
这里查看一些常规想法。

1

我猜你在使用一些非托管的东西,因为在托管平台上系统(垃圾回收器)会处理碎片问题。

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

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


碎片化与垃圾回收无关。它发生在长期存在的对象分散在堆中,因为它们的分配与短期存在的对象混合在一起。短期存在的东西如何被释放是不重要的。 - dmckee --- ex-moderator kitten
2
好的垃圾收集器将通过移动对象并更新引用来处理碎片化问题。 - Constantin
2
我本来想反对这些或那些事情,但我放弃了。假设你也让GC-plus-plus来调节所有内存访问,这是可以的。我告诉过你我步行三英里去上学的事情吗... - dmckee --- ex-moderator kitten

1

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

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


0

如果您能够准确地确定那些可能会分配大块内存的位置,您可以(在Windows上)直接调用VirtualAlloc而不是通过内存管理器。这将避免在普通内存管理器中出现碎片。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接