5. 第五讲 other issues
5.1 GNU C++
5.2 GNU C++对allocators的描述
之所以谈到容器,因为分配器就是为容器服务的。
::operator new继续往下调用的是malloc。
__gnu_cxx::new_allocator和__gnu_cxx::malloc_allocator没有什么特殊的设计,没有内存池的设计,这就是最容易满足需求的做法。
__gnu_cxx::new_allocator相对来说稍微好一些,因为::operator new可重载。
fixed-size pooling cache固定大小的内存池缓存,就是第二讲中提到的16条链表,每条管理不同大小的内存块,内存块都是8的倍数;
cache就是之前提到的先准备一大块内存,然后慢慢划分,最大的优势是去除cookie,同时因为减少了malloc 的调用,速度上有一些提升,但这不应该是最大的优势;
__gnu_cxx::__mt_alloc是多线程的allocator。
注意测试分配器的三个指标;
C++的数组,是静态的,不是动态的,因此避免了"在运行期添乱、增加开销";
"甚至在program startup 情况下也可使用"的意思是在进入程序员编写的程序main之前(右侧的core dump)就可以使用__gun_cxx::array_allocator了,也就是说还没有准备好动态分配的时候,就已经有__gun_cxx::array_allocator了。不过在VC6下的startup被写成了一个函数mainCRTStartup(),这个函数里的第一个动作就是_heap_init进行内存管理的初始化,除非是在这个动作之前还要做事情,否则"设置在program startup 情况下也可使用"这句话的意义就不大了,因为内存管理的初始化完成后,其他的分配器也可以使用了;
5.3 VS2013 标准分配器与new_allocator
没有做什么额外操作的分配器。
5.4 G4.9 标准分配器与new_allocator
标准库中的默认分配器,没有做什么额外操作的分配器。
5.5 G4.9 malloc_allocator
5.6 G4.9 array_allocator
第二模板参数不管是使用std::tr1::array 还是std::array都一样,因为本质相同,底部是一个C++的数组;
C++的数组是静态的,不需要释放,不需要归还,所以array_allocator里面只有allocate() 函数,如果调用deallocate()则是调用的父类的接口,但是这个接口里面do nothing;
array_allocator<int, array<int, 65536>> myalloc(&my); 调用构造函数,其中myalloc是对象名称;
其中
typedef ARRAY std::array<int, 65536>;
ARRAY* pa = new ARRAY;
1
2
这两行代码等同于上一个图中的int my[65536]; 区别在于,int my[65536]; 是静态数组,而这两行是使用动态分配的方式分配的内存;
5.7 G4.9 debug_allocator
sizeof(size_type)在绝大多数系统中都是4,记录区块的大小;
_S_extra()函数的结果表示额外的内存相当于几个元素;
包裹另一个分配器,让分配的区块还多带extra的空间,用于记录整个区块的大小,扮演的角色类似于cookie;
做内存管理的时候,“阳春”型(什么都没做)是没有用的,真正有用的是设计成内存池,设计成内存池的主要用意是去cookie,也提升了一些效率(减少了malloc的调用次数),去除了cookie,又调用debug_allocator,又包装了一层,这样的意义不大;
5.8 G2.9容器使用的分配器不是 std::allocator 而是 std::alloc
容器使用的分配器都是std::alloc;
特点:只拿内存却不还,不会影响自己,但是可能会影响其他进程;
5.9 G4.9 __pool_alloc 用例
真正有用的分配器是这种智能型的分配器,我们追求的是没有cookie。
5.10 G4.9 bitmap_allocator
容器一次都会只要一个元素;
5.10.1 关于blocks,super-blocks,bitmap,mini-vector
blocks就是客户需要的;
一次性申请 64个blocks 用来后续的供应;
64个blocks + bitmap + use count = super-blocks;
bitmap记录了blocks的使用情况,一个bit位表示1个block,1 表示在手中,0表示给出去,当前的状态是全部都在手中;
use count表示使用了几个block,目前的状态是0个被使用;
block size 是 8 的倍数,8,16,24… 这样的增长,只允许这样的大小,图中假设每个block的size是8,所以super block size = 524 bytes;
__mini_vector中的一个元素表示一个super blocks;
使用了第1个block;
bitmap的变化次序和blocks的变化次序相反,blocks从左往右,bitmap从右往左;
bitmap的最后一个bit变成0;
分配了第二个block;
bitmap的倒数第二个bit变成0;
use count变成2;
使用了63个blocks;
只有最后一个block没有使用,所以对应bitmap的第一位为1,其他都为0;
将倒数第三个block归还;
use count变成62;
相对应的bit为变成1010;
5.10.2 1st super-block用罄,启动 2nd super-block
第二个super-block一共有128个blocks,就需要128个bit,即4个整数(每个整数32位);
第二个super-bloc的第1个block给出去了,所以bitmap[0]的最后1个bit变成了0;
标准库中的vector当空间不够的时候会进行 2 倍的增长,此处的_mini_vector就是实现了一个和标准库中的vector相似功能的容器,这里出现了数据的搬动,_M_start此时的值和只有一个元素的时候的_M_start的值是不一样的;
5.10.3 2nd super-block用罄,启动 3rd super-block
第三个super-block一共有256个blocks,需要256bit来表示每个block被使用的状态,即 8 个整数;
此时_mini_vector需要有第三个单元来控制第三个super-block;
因为_mini_vector是成倍增长的,所以此时有4个单元,但是最后一个单元还没被使用;
每个super-block只为一种value type服务;
图中的蓝色格子,每两格表示一个 entry;
5.10.4 1st super-block 全回收
回收的时候使用了另一个_mini_vector,叫做_S_free_list;
当前的super-block已经是256blocks,因为回收了1st super block,所以下次再分配的时候,分配规模为 128blocks;
回收的vector中只存放64个super-block,如果有第65个super-block回收了,就会归还给O.S;
回收了的super-block要将_mini_vector中的这个entry移除,后面的entry元素要往前推;
5.10.5 2nd super-block 全回收
5.10.6 3rd super-block 全回收
5.11 使用G4.9 分配器
最精巧的两个分配器:__pool_alloc和bitmap_allocator
这是测试程序,列举了每个分配器的使用方式。
————————————————
版权声明:本文为CSDN博主「明朗晨光」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011386173/article/details/121872751