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

时间:2009-01-22 20:32:07

标签: performance optimization memory-management garbage-collection malloc

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

注意:我在这里讨论实时内容。对性能至关重要,我的意思是吞吐量很重要,但延迟并不一定。

编辑:虽然我提到了malloc,但这个问题意图是特定于C / C ++。

12 个答案:

答案 0 :(得分:36)

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

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

答案 1 :(得分:20)

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

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

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

答案 2 :(得分:11)

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

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

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

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

答案 3 :(得分:7)

通常,内存分配的成本可能与锁争用,算法复杂性或大多数应用程序中的其他性能问题相形见绌。总的来说,我认为这可能不是我担心的性能问题的前十名。

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

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

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

答案 4 :(得分:5)

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

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

答案 5 :(得分:4)

Java VM将从操作系统声明和释放内存,几乎与应用程序代码的作用无关。这允许它以大块的形式获取和释放内存,这比在微小的单独操作中更有效率,就像手动内存管理一样。

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

  

哪种语言拥有更快的原始速度   分配性能,Java   语言,还是C / C ++?答案可能会   让你大吃一惊 - 分配现代   JVM比最好的快得多   执行malloc实现。该   新Object()的公共代码路径   HotSpot 1.4.2及更高版本   大约10台机器说明   (数据由Sun提供;请参阅参考资料),   而表现最佳的malloc   C中的实现需要   平均在60到100之间   每次通话指令(Detlefs,et。   人;见资源)。和分配   绩效不是一个微不足道的组成部分   整体表现 - 基准   展示了许多真实世界的C和C ++   程序,如Perl和   Ghostscript,花费20%到30%   他们在malloc的总执行时间   并且免费 - 远远超过   分配和垃圾收集   健康Java的开销   应用

答案 6 :(得分:3)

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

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

这个概念也被大多数关注吞吐量的应用程序使用。

答案 7 :(得分:3)

这是c / c ++的内存分配系统运行最佳的地方。对于大多数情况,默认分配策略是可以的,但可以根据需要进行更改。在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模板也允许您定义自定义分配器。

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

答案 8 :(得分:3)

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

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

对我来说,这意味着你应该在NUMA aware allocators几乎独一无二地看待。

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

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

我的friend在他对FreeBSD内核的暗示中有written的深度。

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

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

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

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

他们确实在某种程度上可以量化,至少对于他们的使用案例,在空间/时间之间的确切折衷是什么,通常他们已经确定了LFH(哦,并且通过LFH的方式显然是一种明显的模式)标准堆)或类似设计的方法基本上消耗了显着更多的内存,但随着时间的推移,可能会使用更少的内存... grafix也很整洁......

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

答案 9 :(得分:2)

根据MicroQuill SmartHeap Technical Specification,“一个典型的应用[...]将其总执行时间的40%用于管理内存”。你可以把这个数字作为一个上限,我个人觉得一个典型的应用程序花费更多的10-15%的执行时间来分配/释放内存。它很少是单线程应用程序的瓶颈。

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

答案 10 :(得分:1)

其他人已经介绍过C / C ++,所以我只想添加一些关于.NET的信息。

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

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

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

答案 11 :(得分:1)

如果您正在谈论Microsoft堆,那么几乎所有人都 off 。同步化可以毫不费力地处理,就像碎片一样。

当前的perredrred堆是LFH,( LOW FRAGMENTATION HEAP),它是vista + OS的默认值,可以在XP上配置,通过gflag,有很多麻烦

很容易避免任何锁定/阻塞/争用/总线带宽问题和

的批次
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。检测和分析堆活动。