首页 文章

内存分配/解除分配瓶颈?

提问于
浏览
43

在典型的真实世界的程序中,内存分配/释放有多大的瓶颈?任何类型的程序通常都很重要的答案是受欢迎的 . malloc / free / garbage收集的正确实现是否足够快,以至于它只是少数极端情况下的瓶颈,或者大多数性能关键型软件都会从尝试保持内存分配量下降或拥有更快的malloc / free /中获益匪浅垃圾收集实施?

注意:我是 not 在这里谈论实时的东西 . 对性能至关重要,我的意思是吞吐量很重要,但延迟并不一定 .

编辑:虽然我提到malloc,但这个问题是 not 打算是C / C特定的 .

12 回答

  • 20

    这很重要,特别是随着碎片的增长,分配器必须更大地搜索您请求的连续区域的更大堆 . 大多数对性能敏感的应用程序通常会编写自己的固定大小的块分配器(例如,它们一次要求操作系统提供16MB的内存,然后将其分成4kb,16kb的固定块等)以避免此问题 .

    在游戏中,我看到对malloc()/ free()的调用消耗了高达15%的CPU(写得不好的产品),或者使用精心编写和优化的块分配器,只需5% . 鉴于游戏必须具有60赫兹的一致吞吐量,使其停滞500毫秒而垃圾收集器偶尔运行是不切实际的 .

  • 3

    几乎每个高性能应用程序现在都必须使用线程来利用并行计算 . 这是编写C / C应用程序时真正的内存分配速度杀手所在 .

    在C或C应用程序中,malloc / new必须为每个操作锁定全局堆 . 即使没有争用锁也远非自由,应尽可能避免 .

    Java和C#在这方面做得更好,因为线程从一开始就被设计,内存分配器在每个线程池中工作 . 这也可以在C / C中完成,但它不是自动的 .

  • 2

    首先,因为你说malloc,我假设你在谈论C或C.

    内存分配和释放往往是实际程序的重要瓶颈 . 当你分配或释放内存时,很多东西都在“引擎盖下”,所有这些都是系统特定的;内存实际上可能被移动或碎片整理,页面可能会被重新组织 - 没有独立于平台的方式来了解其影响 . 某些系统(如许多游戏控制台)也不进行内存碎片整理,因此在这些系统上,当内存变得支离破碎时,您将开始出现内存不足错误 .

    一个典型的解决方法是尽可能预先分配尽可能多的内存,并在程序退出之前保持原样 . 您可以使用该内存来存储大型单片数据集,也可以使用内存池实现以块的形式发送它 . 出于这个原因,许多C / C标准库实现会自己执行一定量的内存池 .

    但是,没有两种方法 - 如果你有一个对时间敏感的C / C程序,那么做大量的内存分配/释放会导致性能下降 .

  • 5

    通常,内存分配的成本可能与锁争用,算法复杂性或 most 应用程序中的其他性能问题相形见绌 . 总的来说,我担心的是'd say this is probably not in the top-10 of performance issues I'd .

    现在, grab 非常大的内存块可能是一个问题 . grab 但不能正常摆脱记忆是我要担心的事情 .

    在基于Java和JVM的语言中,新的对象现在非常非常非常快 .

    这是一个体面的文章,由一个知道他的东西的人在底部的一些参考更多相关的链接:http://www.ibm.com/developerworks/java/library/j-jtp09275.html

  • 4

    在Java(以及可能具有体面GC实现的其他语言)中分配对象非常便宜 . 在SUN JVM中,它只需要10个CPU周期 . C / c中的malloc要贵得多,只是因为它需要做更多的工作 .

    甚至Java中的分配对象也非常便宜,对于Web应用程序的许多用户而言,这样做仍然会导致性能问题,因为将触发更多的垃圾收集器运行 . 因此,由于GC的重新分配导致Java中的分配存在间接成本 . 这些成本难以量化,因为它们非常依赖于您的设置(您拥有多少内存)和您的应用程序 .

  • 1

    一个Java VM将从操作系统声明和释放内存,几乎与应用程序代码的作用无关 . 这允许它以大块的形式获取和释放内存,这比在小型单独操作中执行操作要高得多,就像手动内存管理一样 .

    This article写于2005年,JVM风格的内存管理已经走在了前面 . 从那时起,情况才有所改善 .

    哪种语言拥有更快的原始分配性能,Java语言或C / C?答案可能会让您感到惊讶 - 现代JVM中的分配远远快于性能最佳的malloc实现 . HotSpot 1.4.2及更高版本中新Object()的公共代码路径大约是10个机器指令(Sun提供的数据;请参阅参考资料),而C中表现最佳的malloc实现平均需要每次调用60到100条指令( Detlefs,et.al . ;请参阅参考资料) . 分配性能不是整体性能的一个微不足道的组成部分 - 基准测试表明,许多真实的C和C程序,如Perl和Ghostscript,在malloc和free中花费的总执行时间的20%到30% - 远远超过 Health 的Java应用程序的分配和垃圾收集开销 .

  • 11

    在性能方面分配和释放存储器是相对昂贵的操作 . 现代操作系统中的调用必须一直到内核,以便操作系统能够处理虚拟内存,分页/映射,执行保护等 .

    另一方面,几乎所有现代编程语言都将这些操作隐藏在使用预分配缓冲区的“分配器”之后 .

    大多数应用程序也使用此概念,这些应用程序关注吞吐量 .

  • 35

    我知道我早些时候回答说,这是对另一个答案的回答,而不是你的问题 .

    要直接与您联系,如果我理解正确,您的性能使用案例标准就是吞吐量 .

    对我而言,这意味着你应该在NUMA aware allocators看几乎是独一无二的 .

    以前的参考文献都没有; IBM JVM论文,Microquill C,SUN JVM . 涵盖这一点,所以我非常怀疑他们今天的应用程序,至少在AMD ABI上,NUMA是卓越的内存CPU管理器 .

    把手放下;现实世界,假世界,无论世界...... NUMA意识到内存请求/使用技术更快 . 不幸的是,我目前正在运行Windows,而我还没有找到linux中提供的“numastat” .

    我的friend在他对FreeBSD内核的实施中已经深入探讨了这个问题 .

    尽管我能够在远程节点上显示非常大量的本地节点内存请求(强调显而易见的性能优势),但你可以对自己进行基准测试,这可能是你需要的东西 . 因为你的表现特征是非常具体的 .

    我确实知道,在很多方面,至少早期的5.x VMWARE至少在那个时候变得非常糟糕,因为没有利用NUMA,经常要求来自远程节点的页面 . 然而,VM在内存隔离或容器化方面是一种非常独特的野兽 .

    我引用的参考文献之一是微软针对AMD ABI的API实现,它具有NUMA分配专用接口,供用户土地应用程序开发人员利用;)

    这是一个相当新的analysis,visual和all,来自一些浏览器附加开发人员,他们比较了4种不同的堆实现 . 当然,他们最终得分(奇怪的是,进行测试的人通常表现出最高分) .

    它们在某些方面确实可以量化,至少对于它们的用例来说,在空间/时间之间确切的权衡是什么,通常他们已经确定了LFH(哦,而且LFH的方式显然只是标准堆的模式)或类似设计的方法基本上消耗了显着更多的内存,但随着时间的推移,可能最终使用更少的内存... grafix也很整洁...

    然而,我会认为在你很好地理解它之后根据你的典型工作量选择HEAP实施;)是一个好主意,但为了更好地理解你的需求,首先确保你的基本操作是正确的,然后再优化这些几率;

  • 3

    这是c / c的内存分配系统运行最佳的地方 . 默认分配策略是OK对于大多数情况,但可以根据需要进行更改 . 在GC系统中,您无法改变分配策略 . 当然,需要付出代价,而且需要跟踪分配并正确地释放它们 . C更进一步,可以使用new运算符为每个类指定分配策略:

    class AClass
    {
    public:
      void *operator new (size_t size); // this will be called whenever there's a new AClass
       void *operator new [] (size_t size); // this will be called whenever there's a new AClass []
      void operator delete (void *memory); // if you define new, you really need to define delete as well
      void operator delete [] (void *memory);define delete as well
    };
    

    许多STL模板也允许您定义自定义分配器 .

    与所有与优化相关的事情一样,您必须首先通过运行时分析确定在编写自己的分配器之前内存分配是否确实是瓶颈 .

  • 3

    根据MicroQuill SmartHeap Technical Specification,"a typical application [...] spends 40% of its total execution time on managing memory" . 你可以把这个数字作为一个上限,我个人觉得一个典型的应用程序花费更多的10-15%的执行时间来分配/释放内存 . 它很少是单线程应用程序的瓶颈 .

    在多线程C / C应用程序中,标准分配器由于锁争用而成为问题 . 这是您开始寻找更具可扩展性的解决方案的地方 . 但请记住Amdahl's Law .

  • 1

    其他人已经介绍了C / C所以我只是在.NET上添加一些信息 .

    在.NET中,堆分配通常非常快,因为它只是 grab 堆的第0代内存中的内存 . 显然,这不能永远继续下去,这就是垃圾收集的用武之地 . 垃圾收集可能会显着影响应用程序的性能,因为在压缩内存期间必须暂停用户线程 . 完整收集越少越好 .

    您可以采取各种措施来影响.NET中垃圾收集器的工作负载 . 通常,如果你有大量的内存引用,垃圾收集器将不得不做更多的工作 . 例如 . 通过使用邻接矩阵而不是节点之间的引用来实现图形,垃圾收集器将不得不分析更少的引用 .

    这在您的应用程序中是否真的重要取决于几个因素,您应该在转向此类优化之前使用实际数据分析应用程序 .

  • 7

    如果你在谈论微软堆,几乎所有人都是 off base . 同步化可以毫不费力地处理,就像碎片一样 .

    当前的perfrrred堆是LFH,(16000044 FRAGMENTATION HEAP),它是vista OS中的默认值,可以通过gflag在XP上配置,但是很麻烦

    很容易避免任何锁定/阻塞/争用/总线带宽问题以及与之相关的问题

    HEAP_NO_SERIALIZE
    

    HeapAlloc或HeapCreate期间的选项 . 这将允许您创建/使用堆而无需进入互锁等待 .

    我建议使用HeapCreate创建几个堆,并定义一个宏,也许是mallocx(enum my_heaps_set,size_t);

    会很好,当然,你需要realloc,免费也可以设置为适当的 . 如果你想得到花哨的话,通过评估指针的地址,使free / realloc自动检测它自己的堆处理,或者甚至添加一些逻辑以允许malloc根据它的线程id识别要使用的堆,并构建每线程堆和共享全局堆/池的层次结构 .

    Heap * api由malloc / new内部调用 .

    这是关于一些动态memory management问题的好文章,有些甚至更好references . 检测和分析堆活动 .

相关问题