内存分配/释放瓶颈?
在典型的真实世界的程序中,内存分配/释放有多less瓶颈? 来自任何types的性能通常很重要的程序的答案是受欢迎的。 malloc / free / garbage collection的体面实现是否足够快,以至于它只是在几个极端情况下的一个瓶颈,或者大多数性能关键型软件会从试图保持内存分配量下降或拥有更快的malloc / free /垃圾回收的实现?
注意:我不是在这里谈论实时的东西。 对性能至关重要,我的意思是吞吐量很重要,但延迟并不一定。
编辑:虽然我提到malloc,这个问题并不打算是特定于C / C ++。
这很重要,特别是在碎片增长的情况下,分配者必须在您要求的连续区域的更大的堆上search更难的东西。 大多数对性能敏感的应用程序通常会编写自己的固定大小的块分配器(例如,他们一次向操作系统请求16MB的内存,然后将其分为4kb,16kb等固定块),以避免此问题。
在游戏中,我看到malloc()/ free()的调用消耗了CPU的15%(在写得不好的产品中),或者经过精心编写和优化的块分配器,只有5%。 假设一个游戏的吞吐量必须达到六十赫兹,那么在垃圾收集器偶尔运行的情况下,它会拖延500毫秒,这是不现实的。
几乎每个高性能的应用程序现在都必须使用线程来利用并行计算。 这是编写C / C ++应用程序时真正的内存分配速度杀手所在的地方。
在C或C ++应用程序中,malloc / new必须为每个操作locking全局堆。 即使没有争用,锁也不是免费的,应尽可能避免。
Java和C#在这方面比较好,因为线程是从一开始就devise的,内存分配器是从每个线程池开始工作的。 这也可以在C / C ++中完成,但不是自动的。
首先,因为你说malloc,我假设你在谈论C或C ++。
内存分配和释放往往是现实世界程序的重要瓶颈。 当你分配或释放内存时,很多事情都是“隐藏的”,而且全部都是系统特定的。 内存可能实际上被移动或碎片整理,页面可能被重新组织 – 没有平台无关的方式来知道影响会是什么。 某些系统(比如很多游戏机)也不会进行内存碎片整理,所以在这些系统上,随着内存变得分散,您将开始出现内存不足的错误。
一个典型的解决方法是先分配尽可能多的内存,然后继续执行,直到程序退出。 您可以使用该内存来存储大型的单一数据集,也可以使用内存池实现来分块地进行分配。 正因为如此,许多C / C ++标准库实现会自行执行一定数量的内存池。
没有两种方法,但是如果你有一个时间敏感的C / C ++程序,那么执行大量的内存分配/释放操作会导致性能下降。
一般来说,内存分配的成本在大多数应用程序中可能会受到锁争用,algorithm复杂性或其他性能问题的影响。 总的来说,我可能会说这可能不在我担心的性能问题的前10名。
现在,抓取非常大的内存块可能是一个问题。 抓住但没有正确摆脱记忆是我担心的事情。
在Java和基于JVM的语言中,新的对象现在非常非常快。
这里有一个很好的文章,由一个知道他的东西与底部的一些引用更多相关链接的人: http : //www.ibm.com/developerworks/java/library/j-jtp09275.html
在Java中(以及可能的其他语言与一个体面的GC实现)分配一个对象是非常便宜的。 在SUN JVM中,只需要10个CPU周期。 C / C ++中的malloc要贵得多,只是因为它需要做更多的工作。
甚至Java中的分配对象还是非常便宜的,对于并行Web应用程序的许多用户来说,这样做仍然会导致性能问题,因为会触发更多的垃圾收集器运行。 因此,由GC完成的重新分配导致了Java分配的间接成本。 这些成本很难量化,因为它们非常依赖于你的设置(你有多less内存)和你的应用程序。
一个Java VM将会非常独立地从操作系统中声明和释放内存,这与应用程序代码正在做什么无关。 这使得它能够以大块的方式获取和释放内存,这比使用手动内存pipe理的方式更加有效。
这篇文章是在2005年编写的,JVM式的内存pipe理已经在前面。 从那时起情况才有所改善。
哪种语言拥有更快的原始分配性能,Java语言或C / C ++? 答案可能会让你大吃一惊:在现代JVM中的分配要比性能最好的malloc实现快得多。 在HotSpot 1.4.2及更高版本中新Object()的通用代码path大约是10条机器指令(由Sun提供的数据;参见参考资料),而C中性能最好的malloc实现平均需要每次调用60到100条指令Detlefs等人;参见参考资料)。 分配性能并不是整体性能的微不足道的组成部分 – 基准testing表明许多真实世界的C和C ++程序(如Perl和Ghostscript)在malloc和free中花费了其总执行时间的20%到30% – 远远超过健康的Java应用程序的分配和垃圾收集开销。
在性能方面分配和释放内存是相对昂贵的操作。 现代操作系统中的调用必须一直到内核,以便操作系统能够处理虚拟内存,分页/映射,执行保护等。
另一方面,几乎所有的现代编程语言都将这些操作隐藏在使用预分配缓冲区的“分配器”之后。
这个概念也被大多数重点放在吞吐量的应用程序所使用。
我知道我早些时候回答,但是,这是对另一个答案的答案,而不是你的问题。
要直接与您交谈,如果我理解正确,您的性能用例标准是吞吐量。
对我来说,这意味着你应该几乎排他地看NUMA 知道的 分配器 。
以前的参考文献都没有; IBM JVM论文,Microquill C,SUN JVM。 覆盖这一点,所以我现在非常怀疑他们的应用,至less在AMD ABI上,NUMA是卓越的内存CPUpipe理者。
把手放下; 真实的世界,假的世界,无论世界… NUMA知道内存请求/使用技术更快。 不幸的是,我现在正在运行Windows,而且我还没有find在Linux中可用的“numastat”。
我的一位朋友在他对FreeBSD内核的介绍中深入地写了这个东西。
尽pipe我能够在远程节点之上显示特别大量的本地节点内存请求(强调显而易见的性能吞吐量优势),但您可以对自己进行基准testing,而这可能就是您需要的。因为你的performance特点将是非常具体的。
我知道,从很多方面来看,至less早期的5.x VMWARE至less在那个时候是因为没有充分利用NUMA而频繁地要求远程节点的页面。 但是,当涉及到内存容器化或集装箱化时,虚拟机是一个非常独特的东西。
我引用的一个参考文献是微软的API对AMD ABI的启示,它为用户地面应用程序开发人员开发了NUMA分配专用接口;)
以下是来自一些浏览器插件开发人员的相当新近的分析 ,可视化和全部,他们比较了4种不同的堆实践。 当然,他们所开发的产品是顶级的(奇怪的是,进行testing的人往往performance出最高的分数)。
它们的确在一定程度上涵盖了一些方面,至less对于它们的使用情况来说,在空间/时间之间确切的折衷是什么,通常他们已经确定了LFH(哦,并且顺便说一下,LFH只是标准堆的模式)或类似devise的方法基本上消耗更多的记忆蝙蝠但是随着时间的推移,可能会结束使用较less的内存… grafix也是整洁…
然而,我认为,在你理解之后,根据你的典型工作量来select一个HEAP implmentation)是一个好主意,但是为了理解你的需求,首先要确保你的基本操作是正确的,然后才能优化这些差异;)
这是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模板也允许你定义自定义分配器。
和所有关于优化的事情一样,你必须首先通过运行时分析确定在编写自己的分配器之前内存分配究竟是否是瓶颈。
根据MicroQuill SmartHeap技术规范 ,“一个典型的应用程序花费总执行时间的40%来pipe理内存”。 你可以把这个数字作为一个上限,我个人觉得一个典型的应用程序花费了10-15%的执行时间来分配/释放内存。 它很less是单线程应用程序的瓶颈。
在multithreading的C / C ++应用程序中,由于locking争用,标准分配器成为一个问题。 这是您开始寻找更多可扩展解决scheme的地方。 但请记住阿姆达尔定律 。
其他人已经介绍了C / C ++,所以我只是在.NET上添加一些信息。
在.NET中,堆的分配通常非常快,因为它只是在堆的第零代中抓取内存。 显然,这不能永远持续下去,垃圾收集就是在这里进行的。垃圾收集可能会显着影响应用程序的性能,因为在压缩内存期间用户线程必须暂停。 收集越less越好。
有很多事情可以用来影响.NET中垃圾收集器的工作量。 一般来说,如果你有很多的内存引用,垃圾收集器将不得不做更多的工作。 例如,通过使用邻接matrix而不是节点之间的引用来实现图,垃圾收集器将不得不分析更less的引用。
在应用程序中这是否真的有意义取决于几个因素,您应该在转向这样的优化之前用实际的数据来分析应用程序。
如果你正在谈论微软的堆,几乎所有人都离开了基地。 同步化和分裂一样容易处理。
当前的堆是LFH( 低 碎片堆),它在Vista +操作系统中是默认的,可以通过gflag在XP上configuration,不会出现太多麻烦
很容易避免任何locking/阻塞/争用/总线带来的问题和与
HEAP_NO_SERIALIZE
在HeapAlloc或HeapCreate期间选项。 这将允许您创build/使用堆,而不会进入互锁等待状态。
我会推荐用HeapCreate创build一些堆,并定义一个macros,或许是mallocx(enum my_heaps_set,size_t);
会没事的,当然,你需要realloc,也可以自由设置为合适的。 如果你想变得有趣,通过评估指针的地址,自由/ realloc自动检测堆处理,甚至添加一些逻辑,以允许malloc根据它的线程ID来识别哪个堆使用,并build立每线程堆的分层结构和共享的全局堆/池。
堆* api是由malloc / new在内部调用的。
下面是关于dynamic内存pipe理问题的一篇不错的文章,其中有一些更好的参考 。 仪器和分析堆活动。