c++ 9.2.0 . allocator(4)bitmap_allocator

allocator源码解析之bitmap_allocator:

new_allocator

malloc_allocator

pool_allocator

bitmap_allocator

mt_allocator(待完成)

bitmap_allocator 源代码位于:gcc-9.2.0/libstdc++-v3/include/ext/bitmap_allocator.h, 部分实现在:gcc-9.2.0/libstdc++-v3/src/c++98/bitmap_allocator.cc,用名字上可以看出使用了bitmap(bitmap某一位表示一种状态,用做干啥呢?)通过阅读代码可以理解:bitmap用作内存块(struct _Alloc_block)的使用和释放的标志,bitmap_allocator比_pool_allcoator 高级的地方不会所有申请的内存都放到自己手里,增加了内存返回给CRT的功能。

为了方便理解先介绍几个概念:

)_Alloc_block:bitmap_allocator给容器内存的最小单元;

)block:  bitmap_allocator一次内存申请(向CRT)的单位:包含M个_Alloc_block和相应的管理开销;

)free_list:  bitmap_allocator的父类,负责和CRT交互,负责真正的内存申请和回收, 同时负责全回收block的管理,根据策略决定是否回收给操作系统;

)__mini_vector: 作者实现的类似于std::vector容器;

)   _Block_pair 为std::pair<_Alloc_block*, _Alloc_block*> ,first指向第一个_Alloc_block, second指向最后一个_Alloc_block;

) _S_mem_blocks:  用来管理申请的正在被容器使用的block的管理, 类型为_mini_vector, 存储元素的类型为:Block_pair,

)Bitmap_counter:  用来管理block,能够遍历_S_mem_blocks存储的所有block的bitmap, 然后存储:block在_S_mem_blocks(_M_curr_index)的下标和  当前使用的bitmap的指针(_M_curr_bitmap);

) _S_last_request:  类型为:Bitmap_counter, 保存上次为容器分配内存的block和相应的bitmap;

)_S_block_size 每次分配_Alloc_block的数量,每次分配 _S_block_size*= 2, 当有释放的时候 _S_block_size /= 2;

)  _S_find(_Predicate __p), 查找_S_mem_blocks中满足__p的blocks,返回指向这个block的_mini_vector::iterator ;

)_Ffit_finder :放回当前_Block_pair管理的block是否有可用内存,如果有返回true,并修改成员:_M_data_offset (这是第几个bitmap)和_M_pbitmap(指向有内存bitmap);

)Inclusive_between仿函数, 判断某一个_Alloc_block是否在某一个Block_pair管理的范围内。

block的内存结构

bitmap_allocator一次申请内存的block内存结构图(看懂结构就可以,方便后面的理解)

bitmap_allocator的核心存储

 bitmap_allocator的整个内存的管理方式,bitmap_allocator的内存底层存储在_S_mem_blocks(_mini_vector类型),_ S_mem_blocks每一个元素为:std::pair<_Alloc_block*, _Alloc_block*>,指向每次申请内存块第一个的_Alloc_block和最后一个_Alloc_block

言归正传, 1)从allocate流程逐步分析函数;

allocate(size_type __n)函数:

行1019-1023, 和其他alloator一样对于特大对齐方式的元素,直接调用operator new分配;

行1027 ,__builtin_expect(__n ==  1, true)  ,__builtin_expect(EXP, N),  允许程序员将最有可能执行的分支(EXP == N)告诉编译器。编译器在编译过程中,会将可能性更大的代码紧跟着起面的代码,从而减少指令跳转带来的性能上的下降。

行1028,调用_M_allocate_single_object 分配内存,返回给容器

行1030-1-32, 对于>1个数的内存申请,直接调用operator new 分配;

可以看出bitmap_allocator只有在单个元素内存申请的时候才有效果;

_M_allocate_single_object()函数:

行843~845, _S_last_request 类型为:_Bitmap_counter,  保存上次分配内存的block和其bitmap相关信息, while循环从上次分配内存的block往沿着_S_mem_blocks往后寻找可以分配的_Alloc_block(具体后面看_Bitmap_counter的operator++ 实现)

如果有,_S_last_request 保存可以分配的内的block和bitmap信息

如果没有,_S_last_request._M_curr_bitmap = 0 ;_M_finished()返回True

行847-886,如果_M_finished() == True,  从_S_mem_blocks的头向后查找是否有合适的位置(具体后面_S_find 和Ffit_finder的实现)

        行855-872,如果找到了,blocker信息保存在__bpi(类型为_mini_vector::iterator)里,

                    行859, Bit_scan_forward函数返回_Alloc_block在当前block的当前bitmap中位置,保存在__nz_bit 中:

                   行860,__bit_allocate 会将相应的bitmap的__nz_bit+1 位置置为0;

                  行862, 把__bpi的信息,保存到_S_last_request中,提供给下次allocate使用;

                  行865-867, 取得_Alloc_bloc看的地址 保存在_ret 【具体地址的计算参考内存图,更容易理解】

                  行868-872, _Alloc_block的使用计数+1;

        行878-882, 如果没找到,调用_S_refill_pool()分配内存,并放到_S_mem_blocks的尾部,

同时把block信息保存在_S_last_request中,提供给下次内存分配调用;

行890-903, _M_finished() == false, _S_last_request 保存着要分配的block和bitmap信息,炒作一样: 计算_nz_bit, 置0, 获取地址, 计数+1 ,返回;

_S_refill_pool()函数:

第一个红框:行772-776, 计算需要申请内存的大小;

第二个红框:行778-781, 调用_M_get() 获取内存;行781会把使用_Alloc_block的使用个数初始化为0;

第三个红框:行784-792, 申请的内存用_Block_pair管理,并到_M_mem_blocks的尾部;

第四个红框:行794-797, 对bitmap初始化全1,  行797对:_S_block_size *2 (下次内存申请加倍)

基本完成了allocate的过程分析,指针的+-都要参考block内存图思考,更容易看明白,allcocate使用_M_get一次获取大的block,保存在 _S_mem_blocks中,后续分配都操作这个block, 如果没有内存可以分配给容器,会在_S_refill_pool调用_M_get从新获取一块大的block;有几点需要注意的:

1)使用bitmap是,先使用靠近_Alloc_block内存的bitmap, 位置从低到高使用;_Alloc_block的分配从Block_pair的first中开始使用;这样会方便计算,仔细体会;

2)bitmap的下标(offset)是相对第一个_Alloc_block的,每次计算分配_Alloc_block的位置都需要bitmap的offset和bitmap中bit的位置来计算;

2) 分析deallocate()函数过程:(基本和allocate代码相对应,看起来相对轻松)

deallocate(pointer __9, size_type __n) 

_M_deallocate_single_object(pointer __p)函数:

核心代码第一个红框:行929-948, 解释几点:

_S_last_dealloc_index   是局部性原理的利用,认为这次回收内存的block,下次更有可能;

_displacement是要回收的_Alloc_block与blocks第一个的距离;

核心代码第二个红框:行958-962,调用__bit_free 置1,_Alloc_block计数-1;

核心代码第三个红框:行965-994,解释几点:如果_Alloc_block计数 =0 ,

行969, 下此申请内存的block数减半, _S_block_size  /= 2;

行973,调用_M_insert 插入到free_list中,回收这个block。

行982-994,同时需要善后: 如果当前block回收,_S_last_dealloc_index 必须修改为合理值,_S_last_request如果在回收block后面, 需要修改合理的值;

deallocate函数分析相对简单,回收的善后工作要思考清楚,处理好,往往mem_pool的回收处理问题是内存泄漏的根源

总结一下:从allocate和deallocate函数来看, bitmap_allocator巧妙的设计结构,两次结构:1)free_list负责block的分配、缓存、回收,2) bitmap_allocator通过管理_S_mem_blocks来负责Alloc_block保存分配和回收信息,支持Alloc_block分配和全部block回收后的回收工作。

下面详细理解一些其中用到的一些基础类和函数
free_list

free_list  是bitmap_allocator的基类,用作block的管理,包括申请、缓存、释放;free_list 最多缓存64个block,再多根据策略选择性回收;

释放的block, 存储在  _S_free_list中,类型为_mini_vector

_M_should_i_give, 当申请的__required_size与 __block_size不匹配,计算_max_wastage_percentage<=36【这个36很神奇,作业臆断的吧】,另外按照bitmap_allocator身亲内存的计算方式,除了会出现相等,不会出现不相等的方式。

_M_clear() 会对free_list的内存直接调用::perator delete()进行释放。

_M_get(size_t __sz)函数:

_M_get(size_t __sz), bitmap_allocator像CRT申请内存的接口:

核心代码第二个红框:行94-96,如果找到且合适(不浪费),直接给调用方;

核心带带第一个红框:行62-89, 如果没找到或者不合适,试着直接从CRT分配, 失败则调用_M_clear()释放自己缓存的内存,同事再次尝试从CRT分配;

_M_insert(size_t* __addr), bitmap_allocator上层讲 完全回收的block放到free_list的接口;

_M_validate(size_t* __addr) 实际执行插入的函数:

如果free_list已经满(>=64), 则从当前要回收的block和_S_free_list中选取最大块回收;至多保留64个在_S_free_list中;

如果free_list未满,直接插入;

__mini_vector:类似于std::vector的容器

__mini_vector,自定义的一个类似于std::vector容器的类,支持存储空间自动扩充,内存的申请和释放采用调用::operator new 和::operator delete申请一块连续的内存

__mini_vector靠三个指针来管理:_M_start;  指向__mini_vector 的第一个元素, _M_finish; 指向最后一个元素,_M_end_of_storage;  指向最后一个可用空间。

实际使用中,每个元素为Block_pair(std::pair<_Alloc_block*, _Alloc_block*> ,first第一个_Alloc_block和second为最后一个_Alloc_block的指针。

bits_per_block:

bits_per_block: 表示一个size_t有bit个数,前面提到allocator使用了bitmap,哈哈这是精髓,一个size_t可以存储bits_per_block个_Alloc_block的使用状态,也就是每次申请bits_per_block个_Alloc_block,就需要额外一个size_t的空间存储。

bitmap_allocator按照block申请内存,sizeof(block) = bits_per_block *  sizeof(_Alloc_block)后面可以看到,每次新申请block数=count(上次申请block数) * 2,一旦有回收情况 新申请block数= (上次申请block数)  / 2。

介绍几个函数的定义:

__lower_bound函数, 返回在_first和_last之间满足__comp函数的最小_ForwardIterator。

_AddrPair是一个std:pair类型,负责管理每次分配内存, first为指向第一_Alloc_block的指针, second为指向最后一个_Alloc_block的指针。

__num_blocks表示__ap管理的内存包含_Alloc_block的个数。

__num_bitmaps表示__ap管理内存占用的bitmap数,也就是size_t的个数

仿函数(函数对象):_Inclusive_between、_Functor_Ref、_Ffit_finder

_Inclusive_between, 继承std::unary_functio<typename std::pair<_Tp, _Tp>, bool>(c++11已经废弃),_Inclusive_between的函数参数为:std::pair, 函数返回为:bool, 构造函数需要_Tp类型的参数初始化_M_ptr_value。

在实际使用中模板参数_Tp为指针(_Alloc_block*类型), Block_pair 类型为:std::pair<_Alloc_block*, _Alloc_block*>   first指向第一个可用的_Alloc_block块,second代表指向最后一个_Alloc_block块, 函数的作用:计算_M_ptr_value是否在 当前__bp块内,也就是是否由这个block所管理(用于内存回收)。

_Functor_Ref,没啥特殊,内部保存一个仿函数引用,直接调用。

_Ffit_finder 代码如下,我们分三个部分介绍(代码必须配合内存图看,更清晰)

_Ffit_finder核心代码第一块:介绍两个类成员变量:_M_pbitmap(某一个block的内存管理的bitmap)和_M_data_offset(这个bitmap在内存中的offset)【这个offset相对应的地址为第一个_Alloc_block指针(Block_pair.first),这点很重要】,前面提到一个bitmap由一个size_t类型的实现;

_Ffit_finder核心代码第二块:仿函数的实现(operator()),  仿函数的理解需要借助文章开始介绍的内存出来理解,否则真的懵懵懂懂。 

行359, __diff 表示的Block_pair(__bp)有多少的bitmap, 也就是需要几个size_t, 也就是内存图中N的个数【...】 ;

行361~362,判断当前 __bp的所有_Alloc_block是否已经使用完成, (reinterpret_cast<size_t*>(__bp.first) - (__diff + 1))  为内存图中第二个size_t的指针,  该变量表示 __bp 中已经使用的_Alloc_block 的个数。

行367~378, bitmap初始值为全为1,*rover != 0 表示block仍然有_Alloc_block可以分配,

_M_pbitmap指向有内存block的bitmap, _M_data_offset表示当前是第几个bitmap(第几个block)

_Ffit_finder核心代码第三块:_M_offset() 返回第_M_data_offset个block中的第一个_Alloc_block的地址;

_Bitmap_counter类,封装对【一次申请的内存块】的管理,也实现在【多次申请的内存块(多个如第一个内存图的结构)】bitmap的遍历。

_Bitmap_counter的几个成员:

_M_vbp: 底层_S_mem_blocks的引用;

_M_curr_bmap:  当前block的的bitmap指针;

_M_last_bmap_in_block:  当前内存块的最后一个block的bitmap指针;

_M_curr_index: 当前block在_M_vbp(_S_mem_blocks)中的下标;

_Bitmap_counter的几个函数:_M_reset()、_M_get()、 _M_base()、 _M_offset()、 _M_where() 理解内存布局基本就能理解函数的具体实现。

重点介绍下operator++()函数的实现:如果当前bitmap不是最后一个bitmap,则移动到下一个bitmap, 如果是最后一个bitmap, 调用reset(_M_vbp[_M_curr_index +1] )执行初始化操作;所以_Bitmap_counter实现了在所有申请block上的游走;

几个函数:__bit_allocate、__bit_free、_Bit_scan_forward

__bit_allocate(相应的bitmap置o)

__bit_free(相应的bitmap置1)

_Bitscan_forward( 通过调用__builtin_ctzl来查找第一个为1的位置的便宜量)

c++ 9.2.0 . allocator(4)bitmap_allocator - 简书 (jianshu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值