在典型的现实世界程序中,内存分配/释放有多大瓶颈?欢迎回答任何需要考虑性能的程序类型。像malloc/free/垃圾收集等良好实现是否足够快,以至于只有在少数情况下才会成为瓶颈,或者大多数性能关键软件从尽量减少内存分配量或拥有更快的malloc/free/垃圾收集实现中获益显著?
注意:这里不涉及实时内容。通过输出吞吐量来衡量性能的东西,但延迟不一定重要。
编辑:尽管提到了malloc,但本问题并不是特定于C/C++。
在典型的现实世界程序中,内存分配/释放有多大瓶颈?欢迎回答任何需要考虑性能的程序类型。像malloc/free/垃圾收集等良好实现是否足够快,以至于只有在少数情况下才会成为瓶颈,或者大多数性能关键软件从尽量减少内存分配量或拥有更快的malloc/free/垃圾收集实现中获益显著?
注意:这里不涉及实时内容。通过输出吞吐量来衡量性能的东西,但延迟不一定重要。
编辑:尽管提到了malloc,但本问题并不是特定于C/C++。
随着内存碎片化的增加,分配器需要在更大的堆中更努力地寻找您请求的连续区域,这一点尤为重要。大多数性能敏感的应用程序通常会编写自己的固定大小块分配器(例如,它们一次请求16MB的内存,然后将其分割成4kb、16kb等固定块),以避免这个问题。
在游戏中,我曾经看到过对malloc()/free()的调用消耗了高达15%的CPU资源(在编写不良的产品中),或者通过精心编写和优化的块分配器,只需5%的CPU资源。考虑到游戏必须保持一致的六十赫兹吞吐量,在垃圾回收运行时偶尔出现500毫秒停顿是不可行的。
现在几乎所有的高性能应用程序都必须使用线程来利用并行计算。当编写C/C++应用程序时,这就是真正的内存分配速度杀手。
在C或C++应用程序中,malloc/new必须为每个操作锁定全局堆。即使没有争用,锁也远非免费,应该尽可能避免使用。
Java和C#在这方面做得更好,因为线程从一开始就被设计进去,并且内存分配器从每个线程池中工作。这也可以在C/C++中实现,但不是自动的。
首先,由于您提到了malloc,我假设您是在谈论C或C++。
内存分配和释放往往是现实世界程序的一个重大瓶颈。当您分配或释放内存时,“底层”会发生很多事情,所有这些都是系统特定的;内存实际上可能被移动或碎片化,页面可能会重新组织 - 没有平台无关的方式来知道影响将是什么。某些系统(如许多游戏机)也不进行内存碎片整理,因此在这些系统上,随着内存变得分散,您将开始出现内存不足错误。
一个典型的解决方法是尽可能预先分配大量内存,并保留它直到程序退出。您可以使用该内存来存储大型单块数据集,或者使用内存池实现来分配它。许多C/C++标准库实现出于这个原因会自己执行一定数量的内存池操作。
但是毫无疑问,如果您有一个时间敏感的C/C++程序,并且要执行大量的内存分配/释放,则会降低性能。
在性能方面,分配和释放内存是相对昂贵的操作。现代操作系统中的调用必须全部经过内核,以便操作系统能够处理虚拟内存、分页/映射、执行保护等。
另一方面,几乎所有现代编程语言都将这些操作隐藏在“分配器”后面,这些分配器使用预先分配的缓冲区。
大多数关注吞吐量的应用程序也使用这个概念。
我知道之前已经回答了问题,但那只是对另一篇答案的回应,并非针对你的问题。
直接与你交流,如果我理解正确的话,你的性能使用情况的标准是吞吐量。
在我看来,这意味着你应该几乎完全关注NUMA aware allocators。
早期的参考文献:IBM JVM论文、Microquill C、SUN JVM都没有涉及此点,因此我高度怀疑它们在今天的应用中,至少在AMD ABI方面,NUMA是卓越的内存-CPU管理者。
无论是现实世界、虚拟世界还是其他世界,NUMA感知内存请求/使用技术都更快。不幸的是,我目前正在运行Windows,而我还没有找到在Linux中可用的"numastat"。
尽管我能够展示出现场节点内存请求数量通常非常大,而且优势显而易见(突出了明显的性能吞吐量),但你可以肯定地进行基准测试,因为你的性能特征将是高度特定的。
我知道,在很多方面,至少早期的5.x VMWARE表现相当差,因为它没有利用NUMA,经常从远程节点要求页面。然而,VM是一种非常独特的内存分区或容器化技术。
我引用的参考文献之一是Microsoft针对AMD ABI的API实现,其中有专门为用户应用程序开发人员设计的NUMA分配专用接口;)
这里是一些浏览器插件开发人员最近的分析,包括可视化对比了4种不同的堆实现。自然而然地,他们开发的那个排名第一(有趣的是测试者通常表现出最高的分数)。这是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
};