51

在典型的实际程序中,内存分配/释放有多少瓶颈?欢迎来自性能通常很重要的任何类型的程序的答案。malloc/free/garbage 收集的体面实现是否足够快以至于它只是少数极端情况下的瓶颈,或者大多数性能关键软件会从尝试降低内存分配量或拥有更快的 malloc/free/ 中显着受益垃圾回收实现?

注意:我在这里不是在谈论实时的东西。我所说的性能关键是指吞吐量很重要但延迟并不一定的东西。

编辑:虽然我提到了 malloc,但这个问题并不是针对 C/C++ 的。

4

12 回答 12

41

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

在游戏中,我看到对 malloc()/free() 的调用会消耗多达 15% 的 CPU(在编写不佳的产品中),或者使用精心编写和优化的块分配器,仅消耗 5%。鉴于游戏必须具有 60 赫兹的稳定吞吐量,因此在垃圾收集器偶尔运行时让它停顿 500 毫秒是不切实际的。

于 2009-01-22T20:41:25.603 回答
23

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

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

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

于 2009-01-22T20:49:11.750 回答
11

首先,既然你说 malloc,我假设你在谈论 C 或 C++。

内存分配和释放往往是现实世界程序的一个重要瓶颈。当您分配或取消分配内存时,“幕后”会发生很多事情,而且所有这些都是系统特定的;内存实际上可能被移动或碎片整理,页面可能被重新组织——没有独立于平台的方式来知道影响是什么。一些系统(如许多游戏机)也不进行内存碎片整理,因此在这些系统上,随着内存碎片化,您将开始出现内存不足错误。

一个典型的解决方法是预先分配尽可能多的内存,然后一直使用它直到程序退出。您可以使用该内存来存储大量的单片数据集,或者使用内存池实现将其分块分发。正是出于这个原因,许多 C/C++ 标准库实现自己进行了一定数量的内存池。

不过,没有两种方法——如果你有一个时间敏感的 C/C++ 程序,那么做大量的内存分配/释放会降低性能。

于 2009-01-22T20:47:19.617 回答
7

一般来说,内存分配的成本可能与大多数应用程序中的锁争用、算法复杂性或其他性能问题相比相形见绌。总的来说,我想说这可能不在我担心的前 10 名性能问题之列。

现在,获取非常大的内存块可能是个问题。抓取但没有正确清除记忆是我担心的事情。

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

这是一个了解他的东西的人写的一篇不错的文章,底部有一些参考,指向更多相关链接: http ://www.ibm.com/developerworks/java/library/j-jtp09275.html

于 2009-01-22T20:42:05.257 回答
4

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

即使 Java 中的分配对象仍然非常便宜,对于许多并行 Web 应用程序的用户来说,这样做仍然会导致性能问题,因为会触发更多的垃圾收集器运行。因此,在 Java 中存在由 GC 完成的释放引起的分配的间接成本。这些成本很难量化,因为它们在很大程度上取决于您的设置(您有多少内存)和您的应用程序。

于 2009-01-23T10:21:17.880 回答
4

Java VM 将几乎独立于应用程序代码正在执行的操作从操作系统中声明和释放内存。这允许它以大块的形式获取和释放内存,这比在微小的单个操作中执行此操作要高效得多,就像您通过手动内存管理获得的一样。

这篇文章写于 2005 年,JVM 风格的内存管理已经遥遥领先。从那时起,情况才有所改善。

哪种语言拥有更快的原始分配性能,Java 语言还是 C/C++?答案可能会让您大吃一惊——现代 JVM 中的分配比性能最佳的 malloc 实现要快得多。HotSpot 1.4.2 及更高版本中 new Object() 的公共代码路径大约是 10 条机器指令(数据由 Sun 提供;请参阅参考资料),而 C 中性能最佳的 malloc 实现平均需要每次调用 60 到 100 条指令( Detlefs 等人;参见参考资料)。并且分配性能并不是整体性能的一个重要组成部分——基准测试表明,许多现实世界的 C 和 C++ 程序,例如 Perl 和 Ghostscript,

于 2009-07-06T09:23:58.777 回答
3

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

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

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

于 2009-01-22T20:44:16.890 回答
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 模板也允许您定义自定义分配器。

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

于 2009-01-23T10:40:45.017 回答
3

我知道我之前回答过,但是,这是对其他答案的回答,而不是对您的问题的回答。

直接与您交谈,如果我理解正确,您的性能用例标准是吞吐量。

对我来说,这意味着您应该几乎只关注NUMA 感知 分配器

没有早期的参考资料;IBM JVM 论文、Microquill C、SUN JVM。涵盖这一点,所以我高度怀疑他们今天的应用,至少在 AMD ABI 上,NUMA 是卓越的内存 CPU 管理器。

把手放下; 真实世界、虚假世界、任何世界……NUMA 感知内存请求/使用技术更快。不幸的是,我目前正在运行 Windows,我还没有找到 linux 中可用的“numastat”。

我的一个朋友在他对FreeBSD内核的实现中对此进行了深入的描述。

尽管我能够在远程节点上显示 at-hoc,通常非常大量的本地节点内存请求(强调明显的性能吞吐量优势),您可以自己进行基准测试,这可能是您需要做的因为您的表现特征将非常具体。

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

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

这是一些浏览器插件开发人员比较 4 种不同的堆实现的最新分析,视觉和全部。自然而然地,他们开发的那个会排在首位(奇怪的是,做测试的人经常表现出最高分)。

他们确实在某些方面可以量化地覆盖,至少对于他们的用例,空间/时间之间的确切权衡是什么,通常他们已经确定了 LFH(哦,顺便说一下,LFH 显然只是标准堆的一种模式)或类似设计的方法本质上会立即消耗更多的内存,但是随着时间的流逝,最终可能会使用更少的内存... grafix也很整洁...

但是,我认为,在您充分理解之后根据您的典型工作负载选择 HEAP 实现;)是一个好主意,但要充分了解您的需求,首先要确保您的基本操作是正确的,然后再优化这些零碎的东西;)

于 2009-06-03T07:09:06.673 回答
2

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

在多线程 C/C++ 应用程序中,由于锁争用,标准分配器成为一个问题。这是您开始寻找更具可扩展性的解决方案的地方。但请记住阿姆达尔定律

于 2009-02-02T09:08:46.323 回答
1

如果您谈论的是 Microsoft 堆那么几乎所有的人都不在话下。同步化和碎片化一样轻松处理。

当前 perferrred heap 是 LFH,(LOW FRAGMENTATION HEAP),它是 vista+ 操作系统的默认值,可以在 XP 上通过 gflag 进行配置,没有太多麻烦

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

HEAP_NO_SERIALIZE

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

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

很好,当然,你需要 realloc,free 也可以适当地设置。如果您想花哨,请通过评估指针的地址,甚至添加一些逻辑以允许 malloc 根据其线程 id 识别要使用的堆,并构建 free/realloc 自动检测它自己的堆句柄每个线程堆和共享全局堆/池的层次结构。

Heap* api 由 malloc/new 在内部调用。

这是一篇关于一些动态内存管理问题的好文章,还有一些更好的参考资料。检测和分析堆活动。

于 2009-06-02T11:27:36.797 回答
0

其他人已经涵盖了 C/C++,所以我将添加一些关于 .NET 的信息。

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

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

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

于 2009-02-02T09:31:12.347 回答