我一直听说内存池可以显著提高分配内存时的性能。那么为什么传统的malloc实现没有以某种方式使用它们呢?我知道部分原因是内存池使用固定大小的内存块,但似乎有些不需要,它们唯一需要的就是提前获取一点额外的内存。有没有一种方法可以将它们充分推广到这些目的?
5lhxktic1#
内存池 * 可以 * 比一般的内存分配更有效,但通常只是因为你有关于分配模式的额外信息。也许它们最重要的属性是它们在运行时是确定性的,例如在实时操作系统中尤其重要。举个例子,我曾经编写过一个嵌入式系统,其中我知道需要的最大分配是128字节(以下称为块)。为此,我维护了一组连续的块,并使用一个Map来决定一个块是否空闲。它最初是一个位图,但我们最终通过将每个使用/未使用的标志存储在单独的字节中来获得更高的性能。Map的内存使用量是它的8倍,但是由于池的大小是已知的,并且是合理有限的(大约1000个),这还不算太糟糕。而且它给了我们更快的速度,因为我们不需要做一些琐碎的事情来管理游泳池。我们还添加了其他优化,例如存储第一个空闲块,以便我们可以快速找到它。它很容易维护,因为:
然后,如果您请求的数据块大小大于块大小,它将返回NULL(在该系统中从未发生过这种情况,但出于偏执,我编写了代码以防万一)。如果你要求的东西适合一个块,你会得到一个完整的块(但是,当然,你仍然应该只使用你要求的内存,以防万一我以后想改变块大小或从不同块大小的单独池中分配)。这比当时的通用分配器要快得多,因为它们必须处理不同的请求大小,并担心在释放内存时合并相邻的空闲块。但是它需要额外的知识,事实上,没有分配会超过块的大小。另一种模型是为低于特定大小的请求建立一个池,但在以下情况下恢复为一般分配:
这在大多数情况下都可以提高效率(当然,这取决于您的分配模式),但也允许分配超出此范围。它在每次分配中引入了一点额外的工作,因为您需要评估请求大小和池耗尽,但它 * 仍然 * 可能优于一般情况。顺便说一句,我记得Java字符串中有类似的东西(不确定情况是否仍然如此,我已经很久没有使用Java了)。字符串对象分配中有一个缓冲区,用于存储 * 小 * 字符串,但也可以使用该空间来存储单独分配的字符块的指针(如果它大于内部缓冲区)。这减少了可能大量小字符串的碎片(和解引用),但如果需要,仍然允许更大的字符串。有趣的是,我曾经在CPython源代码中做过一个实验,看看内存池是否可以提高性能,特别是考虑到内存分配的数量。它使用了类似于上面给出的策略,优先从池中分配,但是如果请求的大小超过块大小或者池耗尽,则恢复到原始策略。再一次,它有优化讨论,然后一些。例如,最后释放的块被缓存,因此可以立即分发,而无需对池进行任何搜索,以尝试加快many-times(single-free-then-allocate)模式。然而,即使有各种优化,池和块大小,它似乎对我编写的一些测试代码的性能没有实质性的影响,这让我相信CPython中使用的实际分配器已经相当不错了。而且,刚刚阅读我几周前买的那本好书,我现在知道为什么我没有取得任何进展了。事实证明,CPython * 已经 * 进行了大量优化,包括使用内存池。“内存管理”一章详细介绍了更多的细节,但它基本上只使用普通的分配器(原始域)来获取大块(> 256 K)或特定的非对象相关内存。所有的对象,Python几乎是 all objects:-),都来自对象域(除了一些遗留的东西)。对于这个域,它维护自己的堆,并分配大小与系统页面大小相匹配的竞技场,如果支持,则使用mmap来减少碎片。所有使用过的竞技场都保存在一个双向链表中,空的竞技场保存在一个单向自由链表中。
CPython
many-times(single-free-then-allocate)
mmap
在每个竞技场内,创建4K池(因此每个竞技场64个),并且池只能服务于一种大小的分配,当从该池请求第一次分配时锁定。例如,对于1-16字节的请求将从服务于16字节块的池获得16字节块,33-48字节的请求将来自服务于48字节块的池。请注意,这是针对64位系统,其中块大小为{16, 32, 48, ..., 512}。32位系统的块大小略有不同,为{8, 16, 24, 32, ..., 512}。对于竞技场内的池,它们是:
{16, 32, 48, ..., 512}
{8, 16, 24, 32, ..., 512}
请记住,这三种状态之间的转换都会导致池在列表之间移动。我不会再详细说明了,因为你的头可能会爆炸,就像我的一样:-)简而言之,CPython对象分配总是针对一个 * 特定 * 的块大小,最小的块大小大于或等于您所需要的大小。这些来自服务单个块大小的池(一旦锁定)。这些池存在于为防止碎片化而进行了大量优化的竞技场中。可以根据需要创建竞技场。可以说,这就是为什么我的小实验没有改进CPython的原因:它已经以一种相当复杂但有效的方式进行内存池化,我试图拦截malloc的尝试根本没有任何好处。我的开场白“池化内存 * 可以 * 更有效”“但通常只是因为你有关于分配模式的额外信息”得到了那本书的评论的支持:大多数内存分配请求都很小,并且大小固定。因为PyObject是16字节,PyASCIIObject是42字节,PyCompactUnicodeObject是72字节,而PyLongObject是32字节。(a)CPython Internals如果你感兴趣,我没有任何从属关系,除了我喜欢关于事物如何工作的好的技术书籍。
malloc
PyObject
PyASCIIObject
PyCompactUnicodeObject
PyLongObject
q9rjltbz2#
我已经编写了内存池,有多种方法和权衡。我相信malloc()不会在封面下使用它们(如果这是真的),因为:1.内存池会浪费内存,因为它们可能(经常)使用离散(固定)块大小以提高速度。1.例如:如果你请求12字节,你可能会秘密地得到64字节(假设这是最接近的 block size >= 12字节,并且有适当的 * 对齐填充 *),这取决于内存池的实现。然而,也许malloc()会给予你16字节,这是最接近的 * 对齐要求 *,因此浪费的字节更少。1.注意,内存池将块分配给最接近的 * 对齐块大小 *(意味着它必须是A)对齐 * 和 * B)您允许分配的有效块大小之一)>= n请求的字节,而malloc()只分配给最近的 * 对齐要求 *(通常为alignas(max_align_t),通常为8或16字节对齐,具体取决于体系结构)>= n请求的字节。1.内存池会浪费内存,因为它们可能(取决于实现)有大型Map数组或哈希表(这里有多种方法和权衡),从您想要分配的n bytesMap到您可以从中提取的空闲列表链表(对于下一个块大小>= n字节)。换句话说,就像大多数事情一样,有权衡。我 suspectmalloc()选择了更慢和不确定性,以便:1.充分利用你的RAM,而不是浪费它,给你一个64字节的(例如),每次你要求12字节的块,1.并且不要有大规模(几十个千字节大小)的Map数组,或者对您可以分配的最大块大小的奇怪的最大大小限制,内存池可能会强制执行。内存池经常在速度、RAM使用、块大小和最大块数方面进行自定义,以满足您的特定要求和手头的应用程序。另一方面,malloc()必须是 * 通用的 * 和 * 通用的功能 * 为 * 所有数量的字节可能 * 为您给定的大小的RAM。它有许多不同的限制和要求。说了这么多,我正在考虑编写一些称为fast_malloc()和fast_free()的通用替代品。他们要么有**O(1)通过使用巨大的Map数组将n字节Map到块大小来分配和释放时间复杂度,或者我可以选择使用较少的程序空间和/或RAM但使用二进制搜索将n字节Map到块大小的选项,因此具有O(log m)*时间复杂度, 其中m是您可以分配的可能块大小的数量 *。如果需要的话,我甚至可以让它在运行时使用malloc()在内存池耗尽时扩展内存池--但这不应该在微控制器上或在实时、安全关键、确定性应用程序中完成,在这种情况下,我会禁用该功能,只在编译时静态分配,或者在运行时初始化时分配一次。
malloc()
12
64
16
n
alignas(max_align_t)
n bytes
fast_malloc()
fast_free()
m
1.一个O(log m)时间复杂度(分配时O(log m),但免费时O(1))算法可调块大小内存池我写的执行速度是系统malloc()的1倍~ 3倍(即:我的实现花费了大约33%到大约100%的时间),这取决于允许的块大小和分配的字节数。详情请看我在这个答案下面的评论。1.为了获得最大速度,可以编写一个大型的1:1Map数组,以便在**O(1)时间内直接将n字节(当调用fast_malloc(n)时)Map到index,并将其Map到包含{block_size和ptr_to_free_list}结构的Map数组中。这个1:1O(N_MAX)的大小Map数组用于Map到另一个O(m)的大小Map数组将执行得更快,但代价是在程序空间/闪存中使用更多的内存,可能还有RAM,具体取决于您运行的硬件:微控制器与PC无论如何,确实可以编写一个fast_malloc()实现,它在后台使用内存池,并且具有O(1)的分配和释放时间复杂度,并且如果块大小太大而无法在O(1)**时间内分配,则可以恢复到常规的malloc()(即:用于分配n字节,其中n > N_MAX),在这种情况下,它只会将调用传递给常规的malloc()。
fast_malloc(n)
index
block_size
ptr_to_free_list
n > N_MAX
1.****非常有助于学习一些内存池分配策略的基础知识:http://dmitrysoshnikov.com/compilers/writing-a-pool-allocator/的1.看看这些Google搜索:1.memory pool
1.初步结果表明,我的单线程版本的fast_malloc()比glibc的malloc()快~36 clock_cycles/14 clock_cycles = 2.6倍。
1.然而,我的fast_malloc()的多线程实现目前waaaay慢。显然互斥锁是“疯狂地慢”。但我有一些想法来解决它。我还有很多工作要做,我仍然相信我可以做出比目前任何东西都更快的实现,包括多线程应用程序。当然,它也有取舍,即:存储器空间的低效使用。
1.[我的问答]:In gcc is there any way to dynamically add a function call to the start of main()?-该问题还显示了各种malloc()实现的速度测试结果
58wvjzkj3#
像往常一样,“一切都取决于”。在这种情况下,它主要取决于你所说的“性能”是什么意思。正如其他人已经说过的,内存池通常比标准的malloc和free实现更快,但速度不是一般情况下必须考虑的唯一因素。一般的分配器不应该提前分配 * 太多 * 数据,直到必要时(池通常这样做),它应该分配任意大小的块(池通常不这样做)。内存池在分配大量的小块时更有效,特别是相同大小的小块,因为它们可以作为数组分配,所以一堆块有一个共同的头部,而不是每个块的单独头部。另一方面,通常不会用完整个束,这可以被视为存储器效率的损失。池在malloc/new中可以更快,因为它们几乎可以立即从预先分配的合适大小的块阵列中给予数据块,而不是搜索合适的块进行切片。然而,你不可能有一个池来满足每一种可能的大小,所以通常你会得到比需要的块长一点的块,这也是一些内存损失。它们在free/delete中也可以更快,因为它们只将池中的块标记为已释放,而不需要寻找相邻的块来查看它们是否也是空闲的,并将新释放的块“粘合”到它们上。
free
new
delete
i5desfxk4#
现在几乎所有的malloc实现都使用内存池。只有在请求大量内存时,malloc才会直接从内核请求虚拟内存,并在释放后将该内存返回给系统。在所有其他情况下,malloc以较大的块从系统请求内存。例如,如果您请求4字节的内存,malloc可能会从系统请求4 KB,然后只将该块的前4个字节分配给您。malloc这样做的原因是因为从系统请求内存是昂贵的,这样下次你请求4字节的malloc根本不需要从系统请求内存,而是可以从那个块中分配更多的内存。这也意味着,如果释放了一个分配,malloc不能将该块返回给系统,除非该块中的所有分配都被释放。即使这样,malloc也总是会保留一些备用块。还要注意,您不能从系统请求任意数量的虚拟内存,通常只能请求页面大小的倍数,因此如果没有内部内存池,无论分配有多小,每次分配实际上都是4KB的RAM。因此,malloc总是会请求一个或多个页面,并将其分割成块,实际上分配是从这些块中获取的。这里有一个很好的文章是如何工作的:https://levelup.gitconnected.com/understand-heap-memory-allocation-a-hands-on-approach-775151caf2ea这些块基本上形成了一个内存池。但不是专门的。malloc无法提前知道您需要块的频率或大小。它必须以任意顺序处理任意内存分配。此外,还有一些管理开销,在分配的块中为您的分配找到一个空闲点,并记住这个点现在正在使用,以及在它不再使用时再次将其标记为空闲。当人们说内存池更快时,他们指的是专门的内存池。例如,其中所有块具有相同大小的块(例如,匹配你需要的内存分配的大小),并且存储在一个单链表中,所以找到一个空闲块只是从列表中删除第一个块(所有块都有相同的大小,所以任何块都可以),再次释放它只是将它添加回列表;没有什么比这更简单和更快的了。由于您永远不会将内存返回到系统,因此您永远不必再次从系统请求它,这是内存管理中最慢的部分。这种方法的缺点是,您的代码不断保留比当前需要的更多的内存(并且内存对其他进程不可用或可能导致系统交换),并且所有分配都具有固定的大小,这会白白浪费大量内存。内存池是专用的,malloc必须是通用的,并且对资源是保守的。因此,它不会对大型分配使用池方法,即使对于使用池的小型分配,它也无法与根据您的内存需求定制的简单专用池竞争。
4条答案
按热度按时间5lhxktic1#
内存池 * 可以 * 比一般的内存分配更有效,但通常只是因为你有关于分配模式的额外信息。也许它们最重要的属性是它们在运行时是确定性的,例如在实时操作系统中尤其重要。
举个例子,我曾经编写过一个嵌入式系统,其中我知道需要的最大分配是128字节(以下称为块)。为此,我维护了一组连续的块,并使用一个Map来决定一个块是否空闲。
它最初是一个位图,但我们最终通过将每个使用/未使用的标志存储在单独的字节中来获得更高的性能。Map的内存使用量是它的8倍,但是由于池的大小是已知的,并且是合理有限的(大约1000个),这还不算太糟糕。而且它给了我们更快的速度,因为我们不需要做一些琐碎的事情来管理游泳池。
我们还添加了其他优化,例如存储第一个空闲块,以便我们可以快速找到它。它很容易维护,因为:
然后,如果您请求的数据块大小大于块大小,它将返回NULL(在该系统中从未发生过这种情况,但出于偏执,我编写了代码以防万一)。如果你要求的东西适合一个块,你会得到一个完整的块(但是,当然,你仍然应该只使用你要求的内存,以防万一我以后想改变块大小或从不同块大小的单独池中分配)。
这比当时的通用分配器要快得多,因为它们必须处理不同的请求大小,并担心在释放内存时合并相邻的空闲块。
但是它需要额外的知识,事实上,没有分配会超过块的大小。
另一种模型是为低于特定大小的请求建立一个池,但在以下情况下恢复为一般分配:
这在大多数情况下都可以提高效率(当然,这取决于您的分配模式),但也允许分配超出此范围。它在每次分配中引入了一点额外的工作,因为您需要评估请求大小和池耗尽,但它 * 仍然 * 可能优于一般情况。
顺便说一句,我记得Java字符串中有类似的东西(不确定情况是否仍然如此,我已经很久没有使用Java了)。字符串对象分配中有一个缓冲区,用于存储 * 小 * 字符串,但也可以使用该空间来存储单独分配的字符块的指针(如果它大于内部缓冲区)。这减少了可能大量小字符串的碎片(和解引用),但如果需要,仍然允许更大的字符串。
有趣的是,我曾经在
CPython
源代码中做过一个实验,看看内存池是否可以提高性能,特别是考虑到内存分配的数量。它使用了类似于上面给出的策略,优先从池中分配,但是如果请求的大小超过块大小或者池耗尽,则恢复到原始策略。再一次,它有优化讨论,然后一些。例如,最后释放的块被缓存,因此可以立即分发,而无需对池进行任何搜索,以尝试加快
many-times(single-free-then-allocate)
模式。然而,即使有各种优化,池和块大小,它似乎对我编写的一些测试代码的性能没有实质性的影响,这让我相信CPython中使用的实际分配器已经相当不错了。
而且,刚刚阅读我几周前买的那本好书,我现在知道为什么我没有取得任何进展了。
事实证明,CPython * 已经 * 进行了大量优化,包括使用内存池。“内存管理”一章详细介绍了更多的细节,但它基本上只使用普通的分配器(原始域)来获取大块(> 256 K)或特定的非对象相关内存。
所有的对象,Python几乎是 all objects:-),都来自对象域(除了一些遗留的东西)。
对于这个域,它维护自己的堆,并分配大小与系统页面大小相匹配的竞技场,如果支持,则使用
mmap
来减少碎片。所有使用过的竞技场都保存在一个双向链表中,空的竞技场保存在一个单向自由链表中。在每个竞技场内,创建4K池(因此每个竞技场64个),并且池只能服务于一种大小的分配,当从该池请求第一次分配时锁定。例如,对于1-16字节的请求将从服务于16字节块的池获得16字节块,33-48字节的请求将来自服务于48字节块的池。
请注意,这是针对64位系统,其中块大小为
{16, 32, 48, ..., 512}
。32位系统的块大小略有不同,为{8, 16, 24, 32, ..., 512}
。对于竞技场内的池,它们是:
请记住,这三种状态之间的转换都会导致池在列表之间移动。
我不会再详细说明了,因为你的头可能会爆炸,就像我的一样:-)
简而言之,CPython对象分配总是针对一个 * 特定 * 的块大小,最小的块大小大于或等于您所需要的大小。这些来自服务单个块大小的池(一旦锁定)。这些池存在于为防止碎片化而进行了大量优化的竞技场中。可以根据需要创建竞技场。
可以说,这就是为什么我的小实验没有改进CPython的原因:它已经以一种相当复杂但有效的方式进行内存池化,我试图拦截
malloc
的尝试根本没有任何好处。我的开场白“池化内存 * 可以 * 更有效”“但通常只是因为你有关于分配模式的额外信息”得到了那本书的评论的支持:
大多数内存分配请求都很小,并且大小固定。因为
PyObject
是16字节,PyASCIIObject
是42字节,PyCompactUnicodeObject
是72字节,而PyLongObject
是32字节。(a)CPython Internals如果你感兴趣,我没有任何从属关系,除了我喜欢关于事物如何工作的好的技术书籍。
q9rjltbz2#
我已经编写了内存池,有多种方法和权衡。我相信
malloc()
不会在封面下使用它们(如果这是真的),因为:1.内存池会浪费内存,因为它们可能(经常)使用离散(固定)块大小以提高速度。
1.例如:如果你请求
12
字节,你可能会秘密地得到64
字节(假设这是最接近的 block size >= 12字节,并且有适当的 * 对齐填充 *),这取决于内存池的实现。然而,也许malloc()
会给予你16
字节,这是最接近的 * 对齐要求 *,因此浪费的字节更少。1.注意,内存池将块分配给最接近的 * 对齐块大小 *(意味着它必须是A)对齐 * 和 * B)您允许分配的有效块大小之一)>=
n
请求的字节,而malloc()
只分配给最近的 * 对齐要求 *(通常为alignas(max_align_t)
,通常为8或16字节对齐,具体取决于体系结构)>=n
请求的字节。1.内存池会浪费内存,因为它们可能(取决于实现)有大型Map数组或哈希表(这里有多种方法和权衡),从您想要分配的
n bytes
Map到您可以从中提取的空闲列表链表(对于下一个块大小>=n
字节)。换句话说,就像大多数事情一样,有权衡。我 suspect
malloc()
选择了更慢和不确定性,以便:1.充分利用你的RAM,而不是浪费它,给你一个64字节的(例如),每次你要求12字节的块,
1.并且不要有大规模(几十个千字节大小)的Map数组,或者对您可以分配的最大块大小的奇怪的最大大小限制,内存池可能会强制执行。
内存池经常在速度、RAM使用、块大小和最大块数方面进行自定义,以满足您的特定要求和手头的应用程序。另一方面,
malloc()
必须是 * 通用的 * 和 * 通用的功能 * 为 * 所有数量的字节可能 * 为您给定的大小的RAM。它有许多不同的限制和要求。说了这么多,我正在考虑编写一些称为
fast_malloc()
和fast_free()
的通用替代品。他们要么有**O(1)通过使用巨大的Map数组将n
字节Map到块大小来分配和释放时间复杂度,或者我可以选择使用较少的程序空间和/或RAM但使用二进制搜索将n
字节Map到块大小的选项,因此具有O(log m)*时间复杂度, 其中m
是您可以分配的可能块大小的数量 *。如果需要的话,我甚至可以让它在运行时使用malloc()
在内存池耗尽时扩展内存池--但这不应该在微控制器上或在实时、安全关键、确定性应用程序中完成,在这种情况下,我会禁用该功能,只在编译时静态分配,或者在运行时初始化时分配一次。1.一个O(log m)时间复杂度(分配时O(log m),但免费时O(1))算法可调块大小内存池我写的执行速度是系统
malloc()
的1倍~ 3倍(即:我的实现花费了大约33%到大约100%的时间),这取决于允许的块大小和分配的字节数。详情请看我在这个答案下面的评论。1.为了获得最大速度,可以编写一个大型的1:1Map数组,以便在**O(1)时间内直接将
n
字节(当调用fast_malloc(n)
时)Map到index
,并将其Map到包含{block_size
和ptr_to_free_list
}结构的Map数组中。这个1:1O(N_MAX)的大小Map数组用于Map到另一个O(m)的大小Map数组将执行得更快,但代价是在程序空间/闪存中使用更多的内存,可能还有RAM,具体取决于您运行的硬件:微控制器与PC无论如何,确实可以编写一个
fast_malloc()
实现,它在后台使用内存池,并且具有O(1)的分配和释放时间复杂度,并且如果块大小太大而无法在O(1)**时间内分配,则可以恢复到常规的malloc()
(即:用于分配n
字节,其中n > N_MAX
),在这种情况下,它只会将调用传递给常规的malloc()
。补充阅读:
1.****非常有助于学习一些内存池分配策略的基础知识:http://dmitrysoshnikov.com/compilers/writing-a-pool-allocator/的
1.看看这些Google搜索:
1.memory pool
fast_malloc()
初步速度测试结果:1.初步结果表明,我的单线程版本的
fast_malloc()
比glibc的malloc()
快~36 clock_cycles/14 clock_cycles = 2.6倍。1.然而,我的
fast_malloc()
的多线程实现目前waaaay慢。显然互斥锁是“疯狂地慢”。但我有一些想法来解决它。我还有很多工作要做,我仍然相信我可以做出比目前任何东西都更快的实现,包括多线程应用程序。当然,它也有取舍,即:存储器空间的低效使用。相关
1.[我的问答]:In gcc is there any way to dynamically add a function call to the start of main()?-该问题还显示了各种
malloc()
实现的速度测试结果58wvjzkj3#
像往常一样,“一切都取决于”。
在这种情况下,它主要取决于你所说的“性能”是什么意思。
正如其他人已经说过的,内存池通常比标准的
malloc
和free
实现更快,但速度不是一般情况下必须考虑的唯一因素。一般的分配器不应该提前分配 * 太多 * 数据,直到必要时(池通常这样做),它应该分配任意大小的块(池通常不这样做)。内存池在分配大量的小块时更有效,特别是相同大小的小块,因为它们可以作为数组分配,所以一堆块有一个共同的头部,而不是每个块的单独头部。
另一方面,通常不会用完整个束,这可以被视为存储器效率的损失。
池在
malloc
/new
中可以更快,因为它们几乎可以立即从预先分配的合适大小的块阵列中给予数据块,而不是搜索合适的块进行切片。然而,你不可能有一个池来满足每一种可能的大小,所以通常你会得到比需要的块长一点的块,这也是一些内存损失。它们在
free
/delete
中也可以更快,因为它们只将池中的块标记为已释放,而不需要寻找相邻的块来查看它们是否也是空闲的,并将新释放的块“粘合”到它们上。i5desfxk4#
现在几乎所有的malloc实现都使用内存池。只有在请求大量内存时,malloc才会直接从内核请求虚拟内存,并在释放后将该内存返回给系统。
在所有其他情况下,malloc以较大的块从系统请求内存。例如,如果您请求4字节的内存,malloc可能会从系统请求4 KB,然后只将该块的前4个字节分配给您。malloc这样做的原因是因为从系统请求内存是昂贵的,这样下次你请求4字节的malloc根本不需要从系统请求内存,而是可以从那个块中分配更多的内存。
这也意味着,如果释放了一个分配,malloc不能将该块返回给系统,除非该块中的所有分配都被释放。即使这样,malloc也总是会保留一些备用块。还要注意,您不能从系统请求任意数量的虚拟内存,通常只能请求页面大小的倍数,因此如果没有内部内存池,无论分配有多小,每次分配实际上都是4KB的RAM。因此,malloc总是会请求一个或多个页面,并将其分割成块,实际上分配是从这些块中获取的。
这里有一个很好的文章是如何工作的:https://levelup.gitconnected.com/understand-heap-memory-allocation-a-hands-on-approach-775151caf2ea
这些块基本上形成了一个内存池。但不是专门的。malloc无法提前知道您需要块的频率或大小。它必须以任意顺序处理任意内存分配。此外,还有一些管理开销,在分配的块中为您的分配找到一个空闲点,并记住这个点现在正在使用,以及在它不再使用时再次将其标记为空闲。
当人们说内存池更快时,他们指的是专门的内存池。例如,其中所有块具有相同大小的块(例如,匹配你需要的内存分配的大小),并且存储在一个单链表中,所以找到一个空闲块只是从列表中删除第一个块(所有块都有相同的大小,所以任何块都可以),再次释放它只是将它添加回列表;没有什么比这更简单和更快的了。由于您永远不会将内存返回到系统,因此您永远不必再次从系统请求它,这是内存管理中最慢的部分。
这种方法的缺点是,您的代码不断保留比当前需要的更多的内存(并且内存对其他进程不可用或可能导致系统交换),并且所有分配都具有固定的大小,这会白白浪费大量内存。内存池是专用的,malloc必须是通用的,并且对资源是保守的。因此,它不会对大型分配使用池方法,即使对于使用池的小型分配,它也无法与根据您的内存需求定制的简单专用池竞争。