代码之家  ›  专栏  ›  技术社区  ›  KPexEA

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

  •  13
  • KPexEA  · 技术社区  · 16 年前

    我编写了一个转换器,它将openstreetmap XML文件转换为二进制运行时呈现格式,通常约为原始大小的10%。输入文件大小通常为3GB或更大。输入文件不是一次全部加载到内存中,而是作为点和多边形的收集流进行传输,然后在它们上运行BSP并输出文件。最近,在更大的文件中,它耗尽了内存并死掉(其中一个有1400万个点和100万个多边形)。在这种情况下,我的程序通常使用大约1GB到1.2GB的RAM。我已经尝试将虚拟内存从2增加到8GB(在XP上),但这个更改没有效果。另外,由于这段代码是开放源码的,我希望它可以工作,而不管可用的RAM是什么(尽管速度较慢),它可以在Windows、Linux和Mac上运行。

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

    15 回复  |  直到 10 年前
        1
  •  15
  •   Stack Overflow is garbage    16 年前

    首先,在32位系统上,无论页面文件设置如何,内存都将限制在4GB。(其中,只有2GB可用于Windows上的进程。在Linux上,通常有3GB左右的可用空间)

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

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

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

        2
  •  4
  •   Douglas Leeder    16 年前

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

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

        3
  •  4
  •   Brian R. Bondy    16 年前

    听起来你已经在做 SAX 基于XML处理的方法(一次加载XML而不是全部加载)。

    解决方法几乎总是改变算法,使问题分割成更小的部分。物理上,不要一次分配那么多的内存,只读取你需要的,处理它,然后写出来。

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

    如果你不能拆分你的算法,你可能需要 memory mapped files .

    在最坏的情况下,你可以尝试使用 VirtualAlloc 如果您使用的是Windows系统。如果您使用的是32位系统,您可以尝试使用 Physical Address Extension (PAE) .

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

        4
  •  3
  •   dwc    16 年前

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

        5
  •  2
  •   Stephen Nutt    16 年前

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

        6
  •  1
  •   Doug Boone    16 年前

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

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

    因此,我建议您要么强制用户使用64位内存,要么监视您的虚拟内存,并将最大块大小限制为适合32位操作系统限制的大小。

    我已经在Windows中猛击了32位的墙,但是在Linux中没有处理这些限制的经验,所以我只讨论了Windows方面的事情。

        7
  •  1
  •   Christopher Smith    16 年前

    在32位XP上,最大程序地址空间为2GB。然后,由于dll和驱动程序加载到您的地址空间中,您就有了碎片。最后,还有堆碎片化的问题。

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

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

    您可以通过分配较小的块来减轻地址空间碎片的影响。这将有其他令人讨厌的副作用,但您可以遵循退避策略,在这种策略中,如果您未能成功分配内存,您将获取越来越小的内存块。通常,这个简单的方法会让你得到一个程序,如果它不工作的话,它就会工作,但是剩余的时间会尽可能的好。

    伙计,64位计算听起来不是比其他选择更好吗?

        8
  •  1
  •   Nitin Bhide    16 年前

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

    如果是这样,使用内存分配器可能会有所帮助。您可以使用stl分配器进行快速检查。(过度加载点类的新运算符,并使用stl分配器分配内存,而不是“malloc”或默认的新运算符)。

        9
  •  1
  •   Barry Brown    16 年前

    您可能没有以最佳方式分配和释放内存。正如其他人指出的,你可能在泄露记忆,却不知道。调试和优化内存分配需要时间。

    如果您不想花时间优化内存使用,为什么不尝试 Conservative Garbage Collector ?它是malloc()/new和free()的插件替换。实际上,free()是一个no-op,所以您可以从程序中移除这些调用。相反,如果您按照前面的建议手工优化程序并管理一个内存池,您最终将完成CGC已经为您做的大量工作。

        10
  •  1
  •   Arkadiy    16 年前

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

        11
  •  0
  •   Shay Erlichmen    16 年前

    听起来像是在进行从TXT到二进制的对话,那么为什么需要将整个数据保存在内存中呢?.
    难道你不能从txt(xml)中读取一个原语然后保存到binarystream吗?

        12
  •  0
  •   xtofl Adam Rosenfield    16 年前

    如果您希望与内存大小无关,则需要一个与大小无关的算法。不管你的内存大小如何,如果你没有控制好内存的使用,你就会碰到边界。

    看一看你能用来产生一点输出的最少的信息块。然后考虑一种方法,将输入划分成这个大小的块。

    这听起来很简单,不是吗?(很高兴我不用这么做:)

        13
  •  0
  •   Ash    16 年前

    您不需要切换到64位机器,也不需要其他人建议的1000种机器中的大部分。你需要的是一个更贴心的算法。

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

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

    最后,让我指出复杂的任务需要复杂的度量。如果你认为你能负担得起一台拥有8GB内存的64位机器,那么就使用“把文件读到内存中,处理数据,写输出”算法,即使它需要一天时间才能完成。

        14
  •  0
  •   mike    14 年前

    有一种很好的方法可以做到这一点,就是将一些实例存储到文件中,然后在需要使用它们时获取它们。

    这项技术被许多开源软件(如doxygen)使用,以便在需要大量内存时进行扩展。

        15
  •  0
  •   nmw01223    10 年前

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

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

    实际上,为了方便起见,我仍然在使用xp 32bit。

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

    在32位机器上,我唯一能让它在一个很小的内存空间内工作的方法就是仔细考虑我在做什么,什么时候需要什么,然后将任务分成若干块。内存效率高(从未使用超过~100MB),但速度不快,但这无关紧要——必须多久解析一次XML数据?