如何避免堆碎片?

时间:2008-09-29 21:24:59

标签: performance memory-management heap fragmentation

我目前正在开发一个医学图像处理项目,需要大量的内存。我有什么办法可以避免堆碎片并加快对已加载到内存中的图像数据的访问速度吗?

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

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

9 个答案:

答案 0 :(得分:15)

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

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

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

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

答案 1 :(得分:5)

有答案,但如果不知道问题的细节就很难一般。

我假设是32位Windows XP。

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

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

如果您仍然要用完地址空间,那么您将需要能够根据某种缓存算法进出页面块。选择合适的区块进行分页将非常依赖于您的处理算法,需要仔细分析。

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

祝你好运!

答案 2 :(得分:5)

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

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

答案 3 :(得分:2)

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:1)

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

查看有关此here的一般概念。

答案 6 :(得分:1)

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

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

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

答案 7 :(得分:1)

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

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

答案 8 :(得分:0)

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

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