我目前正在从事一个需要大量内存的医学图像处理项目。我可以做些什么来避免堆碎片并加快对已加载到内存中的图像数据的访问?
该应用程序是用 C++ 编写的,可在 Windows XP 上运行。
编辑:应用程序对图像数据进行一些预处理,如重新格式化、计算查找表、提取感兴趣的子图像......在处理过程中应用程序需要大约 2 GB RAM,其中大约 1.5 GB 可以使用为图像数据。
我目前正在从事一个需要大量内存的医学图像处理项目。我可以做些什么来避免堆碎片并加快对已加载到内存中的图像数据的访问?
该应用程序是用 C++ 编写的,可在 Windows XP 上运行。
编辑:应用程序对图像数据进行一些预处理,如重新格式化、计算查找表、提取感兴趣的子图像......在处理过程中应用程序需要大约 2 GB RAM,其中大约 1.5 GB 可以使用为图像数据。
如果您正在进行医学图像处理,则可能一次分配大块(512x512,每像素 2 字节图像)。如果您在图像缓冲区的分配之间分配较小的对象,那么碎片会咬住您。
对于这个特定的用例,编写自定义分配器并不一定很难。您可以为 Image 对象使用标准 C++ 分配器,但对于像素缓冲区,您可以使用全部在 Image 对象中管理的自定义分配。这是一个快速而肮脏的大纲:
这只是一个简单的想法,有很大的变化空间。主要技巧是避免释放和重新分配图像像素缓冲区。
答案是有的,但如果不了解问题的细节,就很难笼统地说。
我假设是 32 位 Windows XP。
尽量避免需要 100 MB 的连续内存,如果你不走运,一些随机 dll 会通过你的可用地址空间在不方便的点加载自己,从而迅速减少非常大的连续内存区域。根据您需要的 API,这可能很难防止。除了一些“正常”内存使用之外,仅分配几个 400MB 的内存块会导致您无处分配最终的“小”40MB 块,这可能会非常令人惊讶。
另一方面,一次预分配合理大小的块。大约 10MB 左右是一个很好的折衷块大小。如果您可以设法将数据划分为这种大小的块,您将能够合理有效地填充地址空间。
如果您仍然会用完地址空间,您将需要能够基于某种缓存算法将块分页进出。选择正确的块来分页将在很大程度上取决于您的处理算法,并且需要仔细分析。
选择将内容分页到哪里是另一个决定。您可能决定只将它们写入临时文件。您还可以调查 Microsoft 的地址窗口扩展 API。在任何一种情况下,您都需要在应用程序设计中小心清理任何指向即将被分页的东西的指针,否则会发生非常糟糕的事情(tm)。
祝你好运!
如果您要对大型图像矩阵执行操作,您可能需要考虑一种称为“平铺”的技术。这个想法通常是将图像加载到内存中,以便相同的连续字节块不会在一行中包含像素,而是在 2D 空间中包含一个正方形。这背后的基本原理是您将在 2D 中而不是在一条扫描线上执行更多彼此更接近的操作。
这不会减少您的内存使用,但可能会对页面交换和性能产生巨大影响。
如果没有关于问题的更多信息(例如语言),您可以做的一件事是通过重用分配而不是分配、操作和释放来避免分配流失。dlmalloc 之类的分配器比 Win32 堆更好地处理碎片。
您将在这里遇到的是虚拟地址范围限制,在 32b Windows 中最多为您提供 2 GB。您还应该知道,使用 DirectX 或 OpenGL 等图形 API 将使用这 2 GB 的大部分用于帧缓冲区、纹理和类似数据。
32b 应用程序的 1.5-2 GB 很难实现。最优雅的方法是使用 64b 操作系统和 64b 应用程序。即使使用 64b 操作系统和 32b 应用程序,只要您使用LARGE_ADDRESS_AWARE
.
但是,由于您需要存储图像数据,您也可以通过使用文件映射作为内存存储来解决此问题- 这可以通过提交和访问内存的方式来完成,但不使用任何虚拟地址。
在这里猜测您的意思是避免碎片,而不是避免碎片整理。还猜测您正在使用非托管语言(可能是 c 或 C++)。我建议您分配大块内存,然后从分配的内存块中提供堆分配。这个内存池因为包含大块内存,所以不太容易产生碎片。总而言之,您应该实现自定义内存分配器。
在此处查看有关此的一些一般性想法。
我猜您正在使用非托管的东西,因为在托管平台中,系统(垃圾收集器)负责处理碎片。
对于 C/C++,您可以使用其他分配器,而不是默认分配器。(在stackowerflow上有一些关于分配器的线程)。
此外,您可以创建自己的数据存储。例如,在我目前正在进行的项目中,我们有一个用于位图的自定义存储(池)(我们将它们存储在一大块连续的内存中),因为我们有很多,并且我们跟踪堆碎片过大时进行碎片整理。
您可能需要实现手动内存管理。图像数据寿命长吗?如果没有,那么您可以使用 apache Web 服务器使用的模式:分配大量内存并将它们包装到内存池中。将这些池作为函数中的最后一个参数传递,以便它们可以使用池来满足分配临时内存的需要。一旦调用链完成,池中的所有内存都应该不再使用,因此您可以清理内存区域并再次使用它。分配很快,因为它们只意味着向指针添加一个值。释放非常快,因为您将立即释放非常大的内存块。
如果您的应用程序是多线程的,您可能需要将池存储在线程本地存储中,以避免跨线程通信开销。
如果您可以准确地隔离那些您可能分配大块的地方,您可以(在 Windows 上)直接调用 VirtualAlloc 而不是通过内存管理器。这将避免在普通内存管理器中产生碎片。
这是一个简单的解决方案,它不需要您使用自定义内存管理器。