如何避免高内存使用率应用程序中的内存不足? C / C ++

我编写了一个转换器,它接受openstreetmap xml文件并将它们转换为二进制运行时渲染格式,通常约为原始大小的10%。 输入文件大小通常为3GB或更大。 输入文件不会一次性加载到内存中,而是在收集点和多边形时进行流式传输,然后在它们上运行bsp并输出文件。 最近在较大的文件上,它耗尽内存并死掉(有问题的一个有1400万个点和100万个多边形)。 通常我的程序在发生这种情况时使用大约1gb到1.2 gb的ram。 我已经尝试将虚拟内存从2增加到8GB(在XP上),但这种改变没有任何效果。 此外,由于此代码是开源的,我希望无论可用的ram(尽管速度较慢)都可以使用它,它可以在Windows,Linux和Mac上运行。

我可以使用哪些技术来避免内存不足? 处理较小子集中的数据,然后合并最终结果? 使用我自己的虚拟内存类型的处理程序? 还有其他想法吗?

首先,在32位系统上,无论页面文件设置如何,您都将始终限制为4 GB内存。 (其中,在Windows上只有2GB可用于您的进程。在Linux上,您通常可以使用大约3GB)

因此,第一个明显的解决方案是切换到64位操作系统,并编译64位应用程序。 这为您提供了巨大的虚拟内存空间,操作系统将根据需要将数据交换进页面文件以保持工作。

其次,一次分配较小的内存块可能会有所帮助。 与一个1GB的块相比,通常更容易找到4个256MB的可用内存块。

第三,分开问题。 不要一次处理整个数据集,而是尝试一次只加载和处理一小部分。

您是否检查过以确保您没有在任何地方泄漏内存?

由于您的程序可以移植到Linux,我建议在Valgrind下运行它以确保。

听起来你已经在使用基于SAX的XML处理方法(随时加载XML而不是一次性加载XML)。

解决方案几乎总是改变算法,以便将问题分解为更小的部分。 物理上不要一次分配多少内存,只读入你需要的内容,处理它,然后写出来。

有时,您可以在算法中使用硬盘驱动器来扩展内存。

如果你不能分割你的算法,你可能想要一些像内存映射文件 。

在最坏的情况下,如果您使用的是Windows系统,则可以尝试使用VirtualAlloc之类的东西。 如果您使用的是32位系统,则可以尝试使用物理地址扩展(PAE)之类的function 。

您还可以考虑为您的程序设置输入限制,并为32位和64位系统设置不同的输入限制。

我怀疑你的内存问题是将BSP树保留在内存中。 因此,将BSP保留在磁盘上,只在内存中保留一些块。 对于BSP来说,这应该相当容易,因为结构比其他树结构更适合,逻辑应该很简单。 为了既高效又内存友好,你可以拥有一个带有脏标志的缓存,缓存大小设置为可用内存减少一点呼吸空间。

假设您使用的是Windows XP,如果您只是超出了内存限制,并且不希望或没有时间按上面的建议重新编写代码,您可以将/ 3GB开关添加到boot.ini文件中,然后它只是一个设置链接器开关以获得额外1GB内存的问题。

您必须了解虚拟内存与“RAM”的不同之处在于您使用的虚拟内存量是您保留的总量,而实际内存(在Windows中称为工作集)是您已经存储的内存实际修改或锁定。

正如其他人所指出的,在32位Windows平台上,虚拟内存的限制是2千兆字节,除非你设置3千兆字节的特殊标志,并且可以确保代码和你使用的任何库中的所有指针都只使用无符号指针。

因此,无论是强制用户使用64位还是监控虚拟内存,并将最大块大小限制为适合32位操作系统限制的内容,我的建议都是如此。

我已经抨击了Windows中的32位墙,但没有在Linux中解决这些限制的经验,所以我只讨论过Windows方面的问题。

在32位XP上,您的最大程序地址空间为2GB。 然后由于DLL和驱动程序加载到您的地址空间而导致碎片。 最后,您遇到了堆碎片的问题。

您最好的举措就是将其完成并以64位进程运行(在64位系统上)。 突然间,所有这些问题都消失了。 您可以使用更好的堆来缓解堆碎片效应,并且您可以尝试使用VirtualAlloc在一个大的连续块中获取内存(然后您可以从那里管理它!)以阻止DLL /驱动程序对其进行分段。

最后,您可以跨流程拆分BSP。 复杂和痛苦,坦率地说它只是把它放在磁盘上会更容易,但理论上你可以通过让一组进程交换信息来获得更好的性能,如果你可以保持一切都驻留(并假设你比内存更聪明而不是操作系统)可以处理文件缓冲…这是一个很大的if)。 每个进程都需要更少的内存,因此不应该运行到2GB的地址空间限制。 当然,你会更快地烧掉RAM /交换。

您可以通过分配较小的块来减轻地址空间碎片的影响。 这将有其他令人讨厌的副作用,但您可以遵循退避策略,如果您未能成功分配,您将获取越来越小的内存块。 通常,这种简单的方法会为您提供一个在其他情况下无法正常工作的程序,但其余时间可以尽可能地执行。

男孩,64位计算不是比其他选择听起来好得多吗?

你如何为积分分配内存? 你是一次分配一个点(例如pt = new Point)。 然后根据点的大小,可能会浪费一些内存。 例如,在Windows上,内存以16字节的倍数分配,因此即使您要求尝试分配1个字节,OS实际上也会分配16个字节。

如果是这种情况,使用内存分配器可能会有所帮助。 您可以使用STL分配器快速检查。 (过载了Point类的新运算符,并使用STL分配器来分配内存而不是’malloc’或默认的新运算符)。

您可能无法以最佳方式分配和释放内存。 正如其他人所指出的那样,你可能会泄漏记忆并且不知道它。 调试和优化内存分配需要时间。

如果您不想花时间优化内存使用,为什么不尝试使用保守垃圾收集器 ? 它是malloc()/ new和free()的插件替代品。 实际上,free()是一个no-op,所以你可以从你的程序中删除那些调用。 相反,如果你手动优化你的程序并按照之前的建议管理一个内存池,你最终会做很多CGC已经为你做的工作。

您需要输出输出和输入。 如果输出格式不是面向流的,请考虑进行第二次传递。 例如,如果输出文件以数据的校验和/大小开始,则在第一遍中留出空格并稍后搜索/写入该空间。

听起来你正在对二进制对话进行txt,所以为什么你需要将整个数据放在内存中?
你不能只是从txt(xml)读取一个原语然后保存到二进制流吗?

如果要独立于内存大小,则需要与大小无关的算法。 无论你的RAM是多大,如果你没有控制内存使用,你就会碰到边界。

看一下您可能用来产生一点输出的最少量信息。 然后想办法将输入分成这个大小的块。

现在听起来很容易,不是吗? (很高兴我不必这样做:))

您不需要切换到64位计算机,也不需要其他人建议的大部分1000项内容。 你需要的是一个更周到的算法。

以下是您可以采取的一些措施来帮助解决这种情况:

  • 如果您使用的是Windows,请使用文件映射( 示例代码 )。 这将通过单个缓冲区指针访问该文件,就像您在内存中读取整个文件一样,但实际上并没有这样做。 Linux Kernel的最新版本具有类似的机制。
  • 如果可以的话,可以按顺序扫描文件,避免创建内存中的DOM。 这将大大减少您的加载时间和内存要求。
  • 使用池内存! 您可能会有许多微小的对象,例如节点,点和诸如此类的东西。 使用池化内存来帮助(我假设你使用的是非托管语言。搜索池化分配和内存池)。
  • 如果您使用的是托管语言,至少将此特定部分移动到非托管语言并控制内存和文件读取。 托管语言在内存占用和性能方面都有非常重要的开销。 (是的,我知道这被标记为“C ++”…)
  • 尝试设计就地算法,一次只读取和处理最少量的数据,因此内存需求会下降。

最后,我要指出,复杂的任务需要复杂的措施。 如果您认为自己可以买得起具有8GB RAM的64位计算机,那么只需使用“将文件读入内存,处理数据,写入输出”算法,即使需要一天时间才能完成。

有一个很好的技术,是将一些实例存储到文件中,并在需要使用它们之后获取它们。

许多开源软件如Doxygen使用这种技术,当需要大量内存时可以扩展。

这是一个老问题,但是,因为我最近做了同样的事情….

没有简单的答案。 在理想的世界中,您将使用具有巨大地址空间(即64位)和大量物理内存的计算机。 仅仅巨大的地址空间是不够的,或者它只会捶打。 在这种情况下,将XML文件解析为数据库,并通过适当的查询,提取您需要的内容。 很可能这就是OSM本身所做的(我相信这个世界大约是330GB)。

实际上,我仍然使用XP 32bit以便于权宜之计。

这是空间和速度之间的权衡。 你可以在任何数量的内存中做任何事情,只要你不关心它需要多长时间。 使用STL结构,你可以解析你想要的任何东西,但你很快就会耗尽内存。 您可以定义自己的交换分配器,但同样,它会效率低下,因为地图,矢量,集合等并不真正知道您在做什么。

我发现在32位机器上完成所有工作的唯一方法就是仔细考虑我在做什么以及什么时候需要将任务分成几块。 内存效率(从不使用超过~100MB)但不是非常快,但后来无关紧要 – 人们多久需要解析XML数据?