空间配置器
一级空间配置器 || 二级空间配置器
默认先走二级然后判断
二级空间配置器
一个指针指向start_free然后start_free向后移动,相当于哈希桶的头删和头插
8byte:切大补小
C++的二级空间配置器按照8字节(或者更大的倍数)切分内存的原因有以下几点:
-
内存对齐:许多计算机体系结构要求数据在内存中的地址是对齐的,即数据的起始地址必须是某个特定值的倍数。按照8字节切分内存可以确保分配的内存块的起始地址满足对齐要求,从而提高内存访问的效率。
-
减少内存碎片:内存碎片是指分配的内存块之间存在的未使用的小块内存。通过按照8字节切分内存,可以减少内存碎片的产生。如果按照较小的单位(如1字节)切分内存,会导致更多的内存碎片,降低内存的利用率。
-
提高内存分配的效率:按照8字节切分内存可以提高内存分配的效率。二级空间配置器使用了一些数据结构(如自由链表)来管理内存池,按照固定大小的块进行切分可以简化数据结构的设计和操作,从而提高内存分配的速度。
需要注意的是,按照8字节切分内存并不是绝对的规定,具体的实现可能会根据不同的编译器、操作系统和硬件平台进行调整。但按照8字节切分内存是比较常见的做法,可以在大多数情况下获得较好的性能和内存利用率。
好的,我来具体解释一下被还回来的小空间是如何被申请的大空间利用的过程。以下是一个简化的示意图:
+-----------------------+
| 大空间 |
+-----------------------+
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-----------------------+
-
初始状态:大空间被划分为多个小空间,每个小空间的状态为已分配或未分配。
+-----------------------+
| 大空间 |
+-----------------------+
| | | | | | |
| | A | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-----------------------+ -
释放小空间:当程序不再需要某个小空间时,将其释放回内存池。
+-----------------------+
| 大空间 |
+-----------------------+
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-----------------------+ -
标记小空间为可用:二级空间配置器会将被释放的小空间标记为可用。
+-----------------------+
| 大空间 |
+-----------------------+
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-----------------------+
可用 可用 可用 -
分配大空间:当程序需要申请大空间时,二级空间配置器会查找可用的连续小空间。
+-----------------------+
| 大空间 |
+-----------------------+
| | | | | | |
| | A | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-----------------------+ -
标记小空间为已分配:二级空间配置器会将被分配的小空间标记为已分配,并从内存池中移除。
+-----------------------+
| 大空间 |
+-----------------------+
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
| | | | | | |
+-----------------------+
通过这个过程,被还回来的小空间可以被申请的大空间利用,从而实现内存的高效利用。
需要注意的是,实际的内存管理过程可能更加复杂,涉及到内存碎片的合并、内存池的扩展等操作。上述示意图仅用于说明基本的内存分配和释放过程。
不是所有进程都公用一个空间配置器。在操作系统中,每个进程都有自己独立的虚拟地址空间,这意味着每个进程都有自己的内存管理单元。
每个进程的内存管理单元负责管理该进程的内存分配和释放。这包括将进程所需的内存从操作系统中分配出来,并在进程不再需要时将其释放回操作系统。
因此,每个进程都有自己的空间配置器,用于管理其内存分配和释放。这样可以确保每个进程的内存操作互不干扰,提高了系统的安全性和稳定性。
需要注意的是,不同进程之间可以通过进程间通信的机制共享内存,但共享的内存通常是通过特殊的方式进行管理,而不是直接使用一个公用的空间配置器。
碎片(一部分一部分还)
空间配置器的碎片指的是内存中的小块未被使用的空间,这些空间可能散落在已分配的内存块之间,无法被有效利用。为了解决内存碎片的问题,空间配置器通常会采取合并碎片的策略。
具体来说,当一个内存块被释放回空间配置器时,空间配置器会尝试将其与相邻的空闲内存块进行合并,以形成更大的连续空闲内存块。这个过程称为内存碎片的合并。
合并碎片的策略可以有多种实现方式,以下是一种常见的策略:
-
首次适应(First Fit):空间配置器会从内存池中的第一个空闲内存块开始遍历,找到第一个足够大的空闲内存块来满足分配请求。如果找到了合适的内存块,就将其分割成两部分,一部分用于分配,另一部分保留为新的空闲内存块。
-
循环首次适应(Next Fit):类似于首次适应,但是从上一次分配的位置开始遍历内存池,直到找到合适的内存块。这样可以减少遍历的次数。
-
最佳适应(Best Fit):空间配置器会遍历整个内存池,找到能够满足分配请求并且大小最接近的空闲内存块。这样可以最大程度地减少内存碎片。
-
最坏适应(Worst Fit):空间配置器会遍历整个内存池,找到能够满足分配请求并且大小最大的空闲内存块。这样可以保留更多的小空闲块,但是可能会导致更多的内存碎片。
无论采用哪种策略,合并碎片的目的都是尽可能地利用内存,减少内存碎片的影响。通过合并碎片,空间配置器可以提供更大的连续内存块,从而满足大内存分配的需求。
解决外碎片问题的方法之一是使用内存池(Memory Pool)或内存池分配器(Memory Pool Allocator)。
内存池是一种预先分配一大块连续内存的数据结构,然后根据需要从该内存池中分配小块内存。内存池可以避免频繁向系统申请小块内存的开销,并且可以减少外碎片的产生。
以下是解决外碎片问题的一种基本思路:
-
在程序初始化阶段,分配一大块连续的内存作为内存池。
-
将内存池划分为固定大小的小块内存,可以使用链表或位图等数据结构来管理这些小块内存的分配情况。
-
当需要申请小块内存时,从内存池中找到一个空闲的小块内存进行分配。
-
当小块内存不再使用时,将其标记为空闲,并将其归还到内存池中。
通过使用内存池,可以避免频繁向系统申请小块内存,从而减少了内存碎片的产生。此外,内存池还可以提供连续的内存块,使得可以更容易地申请大块内存。
需要注意的是,使用内存池也会带来一些额外的开销,比如内存池的初始化和管理。因此,在选择是否使用内存池时,需要根据具体的应用场景和需求进行权衡。
STL六大组件
-
容器(Containers):STL提供了多种容器类,如vector、list、deque、set、map等。容器类提供了不同的数据结构,用于存储和管理数据。每种容器类都有自己的特点和适用场景,可以根据需要选择合适的容器类。
-
算法(Algorithms):STL提供了一组常用的算法,如排序、查找、合并、删除等。这些算法可以应用于各种容器类,提供了高效的实现和使用方式。使用STL算法可以简化编程过程,提高代码的可读性和可维护性。
-
迭代器(Iterators):STL提供了迭代器作为容器和算法之间的桥梁。迭代器提供了一种统一的访问容器元素的方式,可以通过迭代器遍历容器中的元素,实现对容器的操作。STL还提供了不同种类的迭代器,如输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器,每种迭代器都有不同的功能和限制。
-
仿函数(Functors):STL中的仿函数是一种可调用对象,类似于函数指针,可以在算法中使用。仿函数可以作为算法的参数,用于定义算法的行为。STL提供了一些内置的仿函数,如比较仿函数、数值仿函数等,同时也可以自定义仿函数。
-
适配器(Adapters):STL提供了适配器用于修改容器或者算法的接口。适配器可以将一种容器或算法的接口转换为另一种接口,使得它们可以互相兼容。STL中常见的适配器有迭代器适配器、函数适配器等。
-
分配器(Allocators):STL提供了分配器用于管理内存的分配和释放。分配器可以为容器提供自定义的内存管理策略,如内存池分配器、堆分配器等。通过分配器,可以控制容器的内存使用方式,提高内存的分配效率和性能。
复习
vector:基于动态数组
list:双向链表
set:平衡二叉搜索树(元素唯一)
map:红黑树
unordered_map:基于哈希表(Hash Table)
std::unordered_set:基于哈希表实现,它存储一组无序的唯一元素。std::unordered_set提供了快速的插入、删除和查找操作,平均情况下的时间复杂度为O(1)。
map和unorder_map,set和unorder_set的本质区别
std::map
和std::unordered_map
,std::set
和std::unordered_set
的本质区别在于它们使用的底层数据结构和提供的操作效率。
std::map
和std::unordered_map
的本质区别:
std::map
基于红黑树实现,它保持了元素的有序性。红黑树是一种自平衡的二叉搜索树,提供了对键的快速查找、插入和删除操作,时间复杂度为O(log n)。std::map
中的元素按照键的顺序进行排序,因此可以用作有序的查找表。std::unordered_map
基于哈希表实现,它使用键的哈希值来确定元素的存储位置。哈希表提供了快速的插入、删除和查找操作,平均情况下的时间复杂度为O(1)。std::unordered_map
中的元素没有固定的顺序,因此不能用作有序的查找表,但它在大多数情况下提供了更快的操作。
std::set
和std::unordered_set
的本质区别:
std::set
基于红黑树实现,它存储一组有序的唯一元素。std::set
提供了高效的插入、删除和查找操作,时间复杂度为O(log n)。std::unordered_set
基于哈希表实现,它存储一组无序的唯一元素。std::unordered_set
提供了快速的插入、删除和查找操作,平均情况下的时间复杂度为O(1)。
因此,std::map
和std::set
适用于需要有序元素或按照键进行排序的场景,而std::unordered_map
和std::unordered_set
适用于对元素顺序没有要求,但需要更快的操作速度的场景。
multi版本:允许键值冗余