为何我们使用内存管理器?

时间:2015-10-01 15:48:53

标签: c++ c memory memory-management

我已经看到很多基于代码库的服务器代码都有基本的(有时是高级的)内存管理器。内存管理器的真正目的是减少malloc调用的次数,还是主要用于内存分析,损坏检查或可能是其他以应用程序为中心的目的。

保存malloc调用的参数是否足够合理,因为malloc本身就是一个内存管理器。我可以理解的唯一性能增益是当我们知道系统总是要求相同大小的内存时。

或者拥有内存管理器的原因是free不会将内存返回给操作系统但会保存在列表中。因此,在进程的生命周期中,如果由于碎片而继续执行malloc / free,则进程的堆使用量可能会增加。

7 个答案:

答案 0 :(得分:5)

malloc是一个通用分配器 - “不慢”“总是快速”更重要。

考虑一个在许多常见情况下可以提高10%的功能,但在少数情况下可能会导致性能显着下降。特定于应用程序的分配器可以避免罕见情况并获得好处。通用分配器不应该。

对malloc 的调用次数外,还有其他相关属性:

分配地点
在当前的硬件上,这很容易成为性能最重要的因素。应用程序具有更多访问模式的知识,并可以相应地优化分配。

<强>多线程
通用分配器必须允许调用malloc并从不同的线程中释放。这通常需要锁定或类似的并发处理。如果堆非常繁忙,则会导致大量争用。

知道某些高频alloc / frees只来自一个线程的应用程序可以使用自己的特定于线程的堆,这不仅可以避免争用这些分配,还可以增加它们的位置并减轻默认分配器的负担

<强>碎片
对于物理内存或地址空间有限的系统上长时间运行的应用程序,这仍然是一个问题。即使没有实际工作集增加,碎片也可能需要来自OS的越来越多的内存或地址空间。对于需要不间断运行的应用程序来说,这是一个重大问题。

上次我更深入地研究分配器(可能是过去的五年),大家一致认为,减少碎片的天真尝试经常与永不缓慢的规则发生冲突。

同样,知道(某些)分配模式的应用程序可能会从默认分配器中承担大量负载。一个非常常见的用例是构建一个语法树或类似的东西:有大量的小分配,它们从不单独释放,只作为一个整体。这种模式可以通过非常简单的分配器有效地提供。

重新安排和诊断
最后,默认分配器的诊断和自我保护功能可能不足以满足许多应用程序的需要。

答案 1 :(得分:4)

为什么我们有自定义内存管理器而不是内置管理器?

第一个原因可能是代码库最初写于20 - 30年前,当提供的代码库没有任何好处且没有人敢改变它时。

但是,正如您所说,因为应用程序需要管理碎片,在启动时获取内存以确保内存始终可用,出于安全性或其他一些原因 - 大多数原因可以通过正确使用内置经理。

答案 2 :(得分:2)

C和C ++旨在被剥离。它们没有做太多没有明确要求的事情,因此当程序要求内存时,它会获得提供内存所需的最小努力。

换句话说,如果你不需要它,你就不需要付钱。

如果需要对内存进行更细粒度的控制,那就是程序员的域。如果程序员希望将裸机速度换成一个能够在目标硬件上提供更高性能的系统,并结合程序通常独特的目标,更好的调试支持,或者只是喜欢使用经理来看的外观和温暖模糊,这取决于他们。程序员要么更聪明地写东西,要么找到第三方库来做他们想做的事。

答案 3 :(得分:2)

您简要介绍了在您的问题中使用内存管理器的许多不同原因。

  

内存管理器的真正目的是减少malloc调用的次数,还是主要用于内存分析,损坏检查或其他以应用程序为中心的目的?

这是一个大问题。任何应用程序中的内存管理器都可以是通用的(如malloc),也可以更具体。内存管理器越专业化,它就可能在它应该完成的特定任务上更有效率。

采用这个过于简化的例子:

#define MAX_OBJECTS 1000

Foo globalObjects[MAX_OBJECTS];

int main(int argc, char ** argv)
{
  void * mallocObjects[MAX_OBJECTS] = {0};
  void * customObjects[MAX_OBJECTS] = {0};

  for(int i = 0; i < 1000; ++i)
  {
    mallocObjects[i] = malloc(sizeof(Foo));
    customObjects[i] = &globalObjects[i];
  }
}

在上面我假装这个全局对象列表是我们的“自定义内存分配器”。这只是为了简化我要解释的内容。

当您使用malloc进行分配时,无法保证它与之前的分配相邻。 Malloc是一个通用的分配器,并且做得很好,但不一定能为每个应用程序提供最有效的选择。

使用自定义分配器,您可以预先为1000个自定义对象分配空间,并且因为它们是固定大小,所以返回您需要的确切内存量以防止碎片并有效地分配该块。

内存抽象和自定义内存分配器之间也存在差异。 STL分配器可以说是抽象模型而不是自定义内存分配器。

请查看此链接,了解有关自定义分配器的更多信息及其有用的原因:gamedev.net link

答案 4 :(得分:1)

我们想要这样做有很多原因,这实际上取决于应用程序本身。事实上,你提到的所有理由都是有效的。

我曾经构建了一个非常简单的内存管理器,它跟踪shared_ptr分配,以便让我看到应用程序端没有正确发布的内容。

我会说坚持你的运行时,除非你需要它没有提供的东西。

答案 5 :(得分:1)

内存管理器基本上用于有效管理 内存预留。通常,进程可以访问有限数量的内存(32位系统中为4GB),因此您必须减去为内核保留的虚拟内存空间(1GB或2GB,具体取决于您的操作系统配置)。因此,实际上该进程可以访问3GB内存,用于保存其所有段(代码,数据,bss,堆和堆栈)。

内存管理器(例如malloc)尝试通过向OS请求新内存页(使用sbrk或mmap系统调用)来满足进程发出的不同内存预留请求。每次发生这种情况都意味着程序执行需要额外的成本,因为操作系统必须寻找一个合适的内存页面来分配给进程(物理内存是有限的,所有正在运行的进程都想使用它),更新进程表(TMP等)。这些操作非常耗时,并且可以实现流程执行和性能。因此,存储器管理器通常尝试请求所需的页面以巧妙地完成过程预留。例如,它可能会要求更多页面以避免在不久的将来调用更多的mmap调用。此外,它试图处理碎片,内存对齐等问题。这基本上卸载了这个责任的过程,否则编写需要动态内存分配的程序的每个人都必须手动执行此操作!

实际上,有些人可能有兴趣手动进行内存管理。对于必须运行24/365的嵌入式或高可用性系统来说就是这种情况。在这些情况下,即使内存碎片很少,在经过很长一段时间(例如1年)后也可能会出现问题。因此,在这种情况下使用的解决方案之一是使用内存池为应用程序对象预先分配内存。每当你需要某个对象的内存后,你只需使用已经保留的内存。

答案 6 :(得分:0)

对于需要长时间或无限期运行的基于服务器或任何应用程序,主要问题是分页内存碎片。在一系列mallocs / new和free / delete之后,分页内存最终会在页面中留下空白,从而浪费空间并最终耗尽虚拟地址空间。微软通过它的.NET框架处理这个问题,偶尔会暂停一个进程来为进程重新打包分页内存。

为了避免在进程中重新打包内存期间减速,服务器类型应用程序可以为应用程序使用多个进程,以便在重新打包一个进程期间,其他进程可以承担更多的负载。