分配器往往非常复杂,并且在实现方式上往往存在显着差异。
你不能用一种常见的数据结构或算法来描述它们,但有一些共同的主题:
- 内存是以大块的形式从系统中获取的——通常一次是兆字节。
- 然后,当您执行分配时,这些块会被分成各种更小的块。与您分配的大小不完全相同,但通常在特定范围内(200-250 字节、251-500 字节等)。有时这是多层的,在你的实际请求之前,你会有一个额外的“中等块”层。
- 控制从哪个“大块”中分离一块是一件非常困难且重要的事情——这会极大地影响内存碎片。
- 为每个这些范围维护一个或多个空闲池(又名“空闲列表”、“内存池”、“后备列表”)。有时甚至是线程本地池。这可以大大加快分配/释放许多类似大小的对象的模式。
- 大分配的处理方式略有不同,以免浪费大量 RAM,也不会被大量池化。
如果您想查看一些源代码,jemalloc是一种现代高性能分配器,应该在其他常见分配器的复杂性方面具有代表性。TCMalloc是另一种常见的通用分配器,他们的网站详细介绍了所有血腥的实现细节。英特尔的线程构建块有一个专门为高并发构建的分配器。
在 Windows 和 *nix 之间可以看到一个有趣的区别。在 *nix 中,分配器对应用程序使用的地址空间具有非常低级的控制。在 Windows 中,您基本上有一个粗略的、缓慢的分配器VirtualAlloc
来作为您自己的分配器的基础。
这导致与 *nix 兼容的分配器通常直接为您提供malloc
/free
实现,其中假设您只对所有内容使用一个分配器(否则它们会互相践踏),而特定于 Windows 的分配器提供额外的功能,留下malloc
/free
单独, 并且可以和谐地使用(例如,您可以使用 HeapCreate 来创建可以与其他人一起工作的私有堆)。
在实践中,这种灵活性的交易使 *nix 分配器在性能方面略有提升。malloc
在 Windows 上看到应用程序故意使用多个堆是非常罕见的——这主要是由于不同的 DLL 使用不同的运行时而导致的free
意外一些记忆来自。