13

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

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

4

15 回答 15

16

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

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

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

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

于 2009-04-12T14:23:28.900 回答
4

听起来您已经在使用基于SAX的 XML 处理方法(在进行时加载 XML,而不是一次全部加载)。

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

您有时可以在算法需要时通过使用硬盘驱动器来扩展内存。

如果你不能拆分你的算法,你可能想要内存映射文件之类的东西。

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

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

于 2009-04-12T14:15:37.517 回答
4

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

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

于 2009-04-12T14:23:17.200 回答
3

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

于 2009-04-12T14:38:50.220 回答
2

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

于 2009-04-12T14:33:59.250 回答
1

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

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

因此,要么强制用户使用 64 位,要么监控您的虚拟内存,并将您的最大块大小限制在适合 32 位操作系统所施加的限制的范围内,这都是我的建议。

我在 Windows 中撞上了 32 位墙,但没有解决 Linux 中这些限制的经验,所以我只讨论了 Windows 方面的事情。

于 2009-04-12T16:02:06.200 回答
1

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

你最好的办法就是结束它并作为 64 位进程运行(在 64 位系统上)。突然间,所有这些问题都消失了。您可以使用更好的堆来减轻堆碎片的影响,并且可以尝试使用 VirtualAlloc 在一个大的连续块中获取您的内存(然后您可以从那里管理它!)以阻止 DLL/驱动程序对其进行碎片化。

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

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

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

于 2009-04-12T16:20:18.590 回答
1

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

如果是这种情况,使用内存分配器可能会有所帮助。您可以使用 STL 分配器进行快速检查。(为 Point 类重载 new 运算符并使用 STL 分配器分配内存,而不是“malloc”或默认的 new 运算符)。

于 2009-04-12T17:34:36.477 回答
1

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

如果您不想花时间优化内存使用,何不试试Conservative Garbage Collector呢?它是 malloc()/new 和 free() 的插件替代品。事实上, free() 是一个无操作的,所以你可以从你的程序中删除那些调用。相反,如果您按照前面的建议手动优化程序并管理内存池,那么您最终将完成 CGC 已经为您完成的大量工作。

于 2009-04-12T18:56:24.830 回答
1

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

于 2009-04-12T19:20:14.007 回答
0

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

于 2009-04-12T14:23:53.160 回答
0

如果你想独立于内存大小,你需要一个独立于大小的算法。无论您的 RAM 有多大,如果您无法控制内存使用情况,您就会碰到边界。

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

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

于 2009-04-12T18:04:53.703 回答
0

你不需要切换到 64 位机器,也不需要别人建议的 1000 种东西中的大部分。你需要的是一个更周到的算法。

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

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

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

于 2009-04-13T14:20:52.517 回答
0

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

当需要大量内存时,许多开源软件(如 Doxygen)都使用这种技术来实现可扩展性。

于 2011-01-26T15:09:47.050 回答
0

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

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

实际上,出于权宜之计,我仍在使用 XP 32bit。

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

我发现让这一切在 32 位机器上以较小的占用空间运行的唯一方法是非常仔细地考虑我在做什么以及何时需要什么,并将任务分解成块。内存效率高(从不使用超过 ~100MB),但速度不是很快,但没关系 - 需要多久解析一次 XML 数据?

于 2014-11-24T09:39:34.983 回答