"内存碎片化"仍然存在问题吗?

10

我有点困惑。在操作系统课程中,我们被告知所有操作系统都通过分页或分段来处理内存碎片化,并且根本没有连续的物理内存分配。操作系统使用不同级别的寻址(逻辑/物理)来避免连续的内存分配。现在这里有很多讨论。我的问题是: 对于支持逻辑寻址的操作系统,在C++编程中是否真的存在这个问题(是否会因为内存碎片而导致任何进程崩溃)?如果是,为什么首先每个操作系统都试图避免连续寻址?


7
有两种层面的碎片化:虚拟进程地址空间中的碎片化和物理内存中的碎片化。你对哪个方面感兴趣? - user1143634
2
一般情况下,C++编程中不存在内存碎片问题。您总是看到“虚拟”内存,并且总是分配连续的虚拟内存块。唯一需要注意的是,按顺序分配的块不一定相邻。 - Serge
2
@Afshin 这要看情况。如果你看现代应用程序,你会发现随着内存没有被释放到操作系统中,它的内存使用量会随着时间增长。你可以说这是由其他原因引起的,但内存碎片化,例如分配的内存块的非连续位置,是其核心原因。你可以查看这个问题。 - user1143634
4
在任何长时间运行的程序中,碎片化都是一个绝对的问题。它有多大的问题取决于分配模式和堆分配器的质量。一些操作系统(例如Windows)具有糟糕的内置堆分配器,随着时间的推移会导致巨大的碎片化,需要使用替代分配器(例如jemalloc)。 - rustyx
2
@Afshin 如果你在谈论物理页面的分配,那么这与操作系统内核的工作方式有关,与用户空间中的C++编程几乎没有任何关系(可以说是“没有”)。至于jemalloc,它试图解决许多问题,但它处理的是用户空间(操作系统之外)的内存分配。 - user1143634
显示剩余10条评论
2个回答

9
有两个层面的碎片化:虚拟进程地址空间中的碎片化和物理内存中的碎片化。
如果你查看任何现代应用程序,你会看到随着内存没有释放给操作系统,它的内存使用量会随时间增长。你可以说这是由其他原因引起的,但内存碎片化(例如分配的内存块的非连续位置)是其中的核心原因。简而言之,内存分配器拒绝将内存释放给操作系统。
如果你对物理内存中的碎片化感兴趣,那么即使内存以页面的形式组织,仍然需要分配物理上连续的内存块。例如,如果你需要避免虚拟内存开销,你可能想使用大页面(在Linux术语中称为“巨大页面”)。x86_64支持4KiB、2MiB和1GiB页面。如果没有所需大小的连续物理内存,你将无法使用它们。
如果你所说的操作系统是“内核”,那么它无法帮助你解决发生在进程地址空间中的碎片化(堆碎片化)问题。C库应该尽量避免碎片化,不幸的是,它并不总是能够做到。请参见链接的问题

内存分配器通常无法释放大块内存,如果其中至少有一些被分配。这里有一个部分解决方案,利用虚拟内存组织中的页面 - 所谓的“惰性释放”机制,由Linux和BSD上的MADV_FREE以及Windows上的DiscardVirtualMemory表示。当您有一个巨大的内存块只被部分使用时,您可以通知内核该内存的一部分不再需要,并且在内存压力下可以将其收回。这是惰性完成的,仅在内存压力下才执行,因为内存释放非常昂贵。但是,出于性能原因,许多内存分配器仍然不使用它。

因此,对于您的问题的答案 - 这取决于您关心程序的效率程度。大多数程序不关心,因为标准分配器只需完成任务。当标准分配器无法高效地完成其工作时,一些程序可能会受到影响。


1
释放内存区域可能不仅需要巧妙的分配,还需要移动仍然存在的内存对象,例如在压缩式垃圾回收中。许多程序仅使用简单的末尾标记(堆栈指针、程序断点),因此操作系统无法在没有诸如madvise之类的扩展的情况下返回空闲内存。其他结构也是可能的,例如Mill、硬件垃圾回收。 - Yann Vernier
我同意你关于内核级编程的观点,即内存管理任务落在程序员自己身上。 但是关于效率,我不同意你的看法。我认为除了标准(内置)定位器之外的内存定位器,并没有解决碎片问题,而是为并行处理提供了“可伸缩性”等方面的优化。 - Afshin
@Afshin 他们试图解决许多问题。许多人愿意放弃碎片化以实现更高的性能。当您看到您的长时间运行的应用程序开始消耗过多内存并且无法在不重新启动整个进程的情况下释放它时,这可能会困扰您。因此,在某些情况下,碎片化确实是一个问题,您至少应该意识到这一点。 - user1143634
@Ivan 他们可以使您的应用程序更高效,但我认为这与分段无关。我可能错了,但对我来说并不令人信服? :) - Afshin
@Afshin,它们可以加快分配和释放内存的速度,但如果分配器没有解决碎片问题,这可能会增加程序的内存消耗。因此,这仍然是一个权衡。如果您盲目地分配大量数据,您可能会遇到分配器无法解决碎片问题的情况。 - user1143634

3

操作系统并没有避免连续内存分配。在顶层,有硬件和软件。硬件资源有限,这里指物理内存。为了共享资源并避免用户程序处理其共享,虚拟地址层应运而生。它只是将连续的虚拟寻址空间映射到稀疏的物理区域。换句话说,0x10000虚拟地址可以在一个进程中指向0x80000物理地址,在另一个进程中则可以指向0xf0000。

分页和交换意味着将一些页面或整个应用程序内存写入磁盘,然后在某个时间点将其带回。它很可能在之后具有不同的物理页面映射。

因此,您的程序始终会看到连续的虚拟寻址空间,但在物理硬件空间中真正是碎片化的。顺便说一下,这是通过常量块大小完成的,并且没有浪费或未使用的内存空洞。

现在,第二级碎片化是由new/malloc函数引起的,与您分配和删除不同大小的内存有关。这会在虚拟空间中分割您的堆。这些函数确保尽可能少的浪费。

因此,在通用的C++(或任何其他语言)编程中,您不需要关心任何内存碎片化。您分配的所有块都保证在虚拟空间中是连续的(不一定在物理上)。


块大小不一定是恒定的。例如,在x86系列中,我们有页面大小扩展。这主要是为了减少跟踪许多页面的开销。 - Yann Vernier
@YannVernier 是的,我不想用x86分页模式和不同的页面大小来复杂化答案。重要的是操作系统负责有效的物理内存使用。当然,如果您请求几个4k页面,然后切换到2M页面,那么也可能会出现碎片化问题。 - Serge
2
在你的通用C++(或任何其他语言)编程中,你不需要关心任何内存碎片。但是,任何开发长时间运行的应用程序(例如游戏)的人都会非常关注内存碎片。此外,内存碎片可能会影响性能。 - Tara

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接