为啥 malloc() 基于链表?
Posted
技术标签:
【中文标题】为啥 malloc() 基于链表?【英文标题】:Why is malloc() based on linked-list?为什么 malloc() 基于链表? 【发布时间】:2015-06-29 07:26:55 【问题描述】:在最坏的情况下,在大小为n
的内存部分(这是正确的术语吗?)上,链表需要O(n)
时间来分配合适大小的内存块。
但是,如果malloc
是基于树的,例如区间树,则只需要O(logn)
时间。此外,一棵树无需额外的时间(就时间复杂度而言)就可以满足这些要求,例如"Find the smallest block of free memory whose size is larger them
x"
、"Always allocate on the borders of free memory"
和"Free only a part of the allocated memory"
。一个缺点可能是释放内存需要O(logn)
时间。
谢谢
ps。看到Data structures for traversable memory pool的问题了,但是作者好像没搞明白。
【问题讨论】:
是什么让您认为malloc
使用了链表?仅仅因为 一个 (许多)实现使用了它?
@JoachimPileborg 我读到ptmalloc2
在链表中使用块,它在最新版本的 glibc 中。
您是否真的尝试过编写自己的 malloc
和 free
函数,使用其他东西,并测量与当前实现相比分配所需的时间。
@MatsPetersson 我写了一个模型,在数组上玩,假装一些“程序”需要内存。
结果如何?整体执行时间是否有很大改善?在琐碎的程序中,我看到malloc
占用了很大一部分时间。但在非平凡的程序中,通常不是花费时间的地方——显然高度依赖于程序的确切功能!但是内存分配至少在过去 40 年里就已经存在了,而且模型在很大程度上是相同的——我相信有些人已经研究过它。
【参考方案1】:
我不知道答案,但这里有一些想法:
绝对没有要求malloc
以特定方式实现。但是,在最坏的情况下,不平衡树与链表一样糟糕。平衡链表需要更多维护。一棵树,每个节点有两个链接,也比单链表占用更多的内存。删除链表中的节点更容易,在末尾插入也很容易。
在大多数系统中,每个malloc
(几乎)恰好有一个free
- 所以如果你通过让另一个变慢来让一个更快,你获得的收益很少。
“下一个分配与上一个相同”也比较常见,这意味着如果最后一个分配在列表中是第一个,这是一个 O(1) 操作。
在实时系统中,桶通常用于分配,因此有许多固定大小,每次从主堆中分配一些东西时,大小都会四舍五入到最接近的较大大小,当释放它进入那个大小的桶(这是一个链表)。如果已经有该大小的空闲元素,则使用该分配。除了分配/释放的速度为 O(1) 之外,这还具有减少碎片的好处——“将所有堆撕成小块,然后不留下任何大块”并非完全不可能,但至少不是每次分配一个字节,直到你在一次分配中获得一半的堆大小,可能会占用大部分内存。
(另外,在 Linux 的 GLIBC 中,超过一定大小的分配根本不会在链表中结束 - 它们直接通过 mmap
分配,并在调用 free
时以 munmap
释放)
最后,算法复杂度并不是一切——在现实生活中,它是实际花费在重要事情上的时间——即使算法有 O(n) 但每个操作都很快,它也可以击败 O(logn)。同样,特别是在 C++ 中,小分配占主导地位,这意味着每个节点的更多内存开销是一个重要因素。
【讨论】:
正因为每个malloc
都有一个free
,所以基于树的malloc
使用O(logn)+O(logn)=O(logn)
时间和链表O(n)+O(1)=O(n)
而且分配平均为 O(n),而不是 O(1),即使使用存储桶也是如此。
是的,但您还必须考虑实际时间。
Buckets 保证 O(1) 的分配和免费 - 因为桶中的项目都是相同大小的,你只需选择最近释放的项目 - 或者挖出一点“新鲜”如果空闲列表中没有任何内容,则堆。但我确信使用效率较低的存储桶来实现某些东西是可能的——总是可以做到的。
你的意思是时间复杂度中的常数可能很大吗?【参考方案2】:
没有说明 malloc 需要基于链表的规范。在平台之间,实现可能会发生变化。在一个平台上,速度可能至关重要,可以实现一棵树,在另一个平台上,内存更昂贵,并且使用链表(或此类)以尽可能节省内存。
【讨论】:
以上是关于为啥 malloc() 基于链表?的主要内容,如果未能解决你的问题,请参考以下文章