【问题标题】:How to avoid running out of memory in high memory usage application? C / C++如何避免在高内存使用应用程序中耗尽内存? C / C++
【发布时间】:2010-10-19 00:22:41
【问题描述】:

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

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

【问题讨论】:

    标签: c++ c memory virtual-memory


    【解决方案1】:

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

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

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

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

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

    【讨论】:

      【解决方案2】:

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

      这种技术被许多开源软件(如 Doxygen)用于在需要大量内存时进行扩展。

      【讨论】:

        【解决方案3】:

        您不需要切换到 64 位机器,也不需要其他人建议的 1000 项中的大多数。你需要的是一个更周到的算法。

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

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

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

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            【解决方案5】:

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

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

            【讨论】:

              【解决方案6】:

              如果你想独立于内存大小,你需要一个独立于大小的算法。不管你的内存有多大,如果你不能控制内存使用,你就会撞到边界。

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

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

              【讨论】:

                【解决方案7】:

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

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

                【讨论】:

                • 我正在使用堆管理器分配点和多边形,因此它们只占用必要的空间并且几乎没有听到因为我的堆分配(在这种情况下)1mb 块并从每个块发出请求
                • 另一个原因可能是“内存碎片”(即内存在小块中可用,但是当您要求“1 Mb”时,连续的 1 MB 块不可用。XML 解析器是否使用“堆管理器” '?可能是 XML 解析器使用标准内存分配并导致碎片?
                【解决方案8】:

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

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

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

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

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

                【讨论】:

                  【解决方案9】:

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

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

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

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

                  【讨论】:

                    【解决方案10】:

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

                    【讨论】:

                      【解决方案11】:

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

                      【讨论】:

                      【解决方案12】:

                      听起来您已经在使用基于 SAX 的 XML 处理方法(在执行过程中加载 XML,而不是一次性加载)。

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

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

                      如果你不能拆分你的算法,你可能想要memory mapped files这样的东西。

                      在最坏的情况下,如果您使用的是 Windows 系统,您可以尝试使用 VirtualAlloc 之类的东西。如果您使用的是 32 位系统,则可以尝试使用 Physical Address Extension (PAE) 之类的内容。

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

                      【讨论】:

                        【解决方案13】:

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

                        【讨论】:

                          【解决方案14】:

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

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

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

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

                          【讨论】:

                          • Windows 可以通过 /LARGEADDRESSAWARE 拥有 3GB 的虚拟空间
                          • 该标志允许进程使用最多 4GB如果操作系统可以提供它。通常,Windows 仍然设置为每个进程只分配 2GB。这也可以更改,冒着驱动程序不稳定的风险,给你 3GB,是的。使用 PAE,您可以获得更多。但 64 位可能是更好的选择。
                          • 恕我直言,第三个选项是最重要的。除了提供对内存的控制之外,它还允许并行处理。
                          • RE - Jalf "With PAE":据我所知,Windows XP 并不真正支持大多数硬件附带的 4 个额外地址位(请参阅 PAE)。我从 wikipedia 的 PAE 页面得到这个,该页面有一个 Microsoft 网页的链接。任何人都可以根据经验确认 winXP 忽略了额外的地址位吗?
                          • @Trevor:我没试过,但我很确定 XP 支持它(当然你必须手动启用它)。但这有关系吗?如今,切换到 64 位可能是更好的解决方案。
                          【解决方案15】:

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

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

                          【讨论】:

                          • 是的,我检查了是否有泄漏,没有。
                          猜你喜欢
                          • 2010-12-15
                          • 2010-12-24
                          • 2019-05-26
                          • 1970-01-01
                          • 2013-07-17
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-06-13
                          相关资源
                          最近更新 更多