如何避免堆碎片化?

时间:2020-03-06 14:53:52  来源:igfitidea点击:

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

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

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

解决方案

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

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

在这里看到一些一般的想法。

我猜测我们使用的是不受管的东西,因为在受管平台中,系统(垃圾收集器)负责处理碎片。

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

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

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

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

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

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

  • 并行的布尔数组,指示是否正在使用相应的图像
  • 要取消分配,请在数组中找到相应的缓冲区并清除布尔值标志

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

有答案,但是如果不了解问题的细节,很难一概而论。

我假设使用32位Windows XP。

尽量避免需要100 MB的连续内存,如果我们不走运,一些随机的dll将通过可用地址空间在不便的点加载自身,从而迅速减少非常大的连续内存区域。根据我们需要的API,很难避免这种情况。令人惊讶的是,除了分配一些"正常"内存使用量之外,仅分配几个400MB的内存块如何使我们无处分配最终的"小" 40MB块。

另一方面,请一次预分配合理大小的块。良好的折衷块大小约为10MB。如果我们可以设法将数据划分为这种大小的块,则可以合理有效地填充地址空间。

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

选择将内容分页到何处是另一个决定。我们可能决定只将它们写入临时文件。我们还可以研究Microsoft的地址窗口扩展API。无论哪种情况,我们都需要在应用程序设计中小心一些,以清除所有指向即将被调出的对象的指针,否则会发生真正的坏事(tm)。

祝你好运!

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

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

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

对于32b应用程序,很难达到1.5-2 GB。最优雅的方法是使用64b OS和64b应用程序。即使使用64b OS和32b应用程序,只要我们使用" LARGE_ADDRESS_AWARE",这在某种程度上也是可行的。

但是,当我们需要存储图像数据时,也可以通过使用文件映射作为内存存储来解决此问题,这可以通过以下方式完成:拥有已提交和可访问的内存,但不使用任何虚拟地址完全没有。

如果我们可以完全隔离那些可能分配大块的位置,则可以(在Windows上)直接调用VirtualAlloc,而不用通过内存管理器。这将避免普通内存管理器中的碎片。

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