一、摘要

  bitmap_allocator是STL空间分配器的其中一种,它采用内存池策略,最多存储64条空闲链表(freelist,实际是一块空间连续的内存区,后面也称为超级块),每条空闲链表存储的内存块(block)个数呈指数递增,内存块大小一致,内存池的阈值总是维持在64,任何时刻内存池的链表个数都不会超过64。bitmap_allocator空间分配器仅适合分配单一对象,超过一个对象的分配,采用operator new分配内存,回收亦然。该分配器通过位图存储方式,位图中的每一位均记录对应内存块的使用情况,0表示已用,1表示可用。同时,每个空闲链表头部记录了该链表总空间大小,总内存块个数等信息。

  bitmap_allocator内存池和每个空闲链表的布局示意图如下:

  total_size为内存链表总内存大小(不计本身,因为该值为free_list内部维护,对bitmap_allocator不可见),total_num为_Alloc_block 使用计数(其值视内存块使用情况而变化,内存块都空闲时为0),bitmap为位图,其中每一位标记_Alloc_block的使用情况,0表示已用,1表示可用。_Alloc_block 为分配的内存块,大小为size_t。

  假设32位机器,size_t为4字节大小,那么0号链表total_size=4+(4*2)+(4*64)= 268,_Alloc_block初始为64个,分配个数视情况而定,每次新申请内存链表则2倍递增,回收入内存池则减半(详见后面代码分析)。

二、各个组件说明

  了解bitmap_allocator的实现细节,得先初步了解其相关的组件(即辅助类)。bitmap_allocator分配和回收内存,依赖于各个组件的协同参与。

1、__mini_vector:精简版的vector实现,用来充当free_list、block_pair 等结构的序列型容器;

2、_Inclusive_between:该类内部重载operator(),来判别指针所处的内存链表,内存块回收时使用;

3、_Functor_Ref:函数对象,即仿函数;

4、_Ffit_finder:根据block_pair 判断该内存链表是否还存在空闲的内存块,定位可用内存块对应的bitmap指针和下标;

5、_Bitmap_counter:位图的管理者,用来实现位图的搜索定位,同时也搜索定位block_pair;

6、free_list:负责内存链表的管理,内存链表的申请和释放都通过它;

7、bitmap_allocator:真正的空间分配器,依赖上述各个组件实现内存单元的申请和释放;

三、__mini_vector

template<typename _Tp>
class __mini_vector
{
__mini_vector(const __mini_vector&);
__mini_vector& operator=(const __mini_vector&); public:
typedef _Tp value_type;
typedef _Tp* pointer;
typedef _Tp& reference;
typedef const _Tp& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef pointer iterator; private:
pointer _M_start; // 指向容器第一个元素
pointer _M_finish; // 指向容器最后一个元素的下一个位置
pointer _M_end_of_storage; // 指向容器末端 size_type
_M_space_left() const throw()
{ return _M_end_of_storage - _M_finish; } _GLIBCXX_NODISCARD pointer
allocate(size_type __n)
{ return static_cast<pointer>(::operator new(__n * sizeof(_Tp))); } void
deallocate(pointer __p, size_type)
{ ::operator delete(__p); } public:
__mini_vector()
: _M_start(0), _M_finish(0), _M_end_of_storage(0) { } size_type
size() const throw()
{ return _M_finish - _M_start; } iterator
begin() const throw()
{ return this->_M_start; } iterator
end() const throw()
{ return this->_M_finish; } reference
back() const throw()
{ return *(this->end() - 1); } reference
operator[](const size_type __pos) const throw()
{ return this->_M_start[__pos]; } void
insert(iterator __pos, const_reference __x); void
push_back(const_reference __x)
{
// 如果容器还有剩余空间,直接向末端插入元素,否则调用insert重新申请空间,所有元素迁移,__x放置末端
if (this->_M_space_left())
{
*this->end() = __x;
++this->_M_finish;
}
else
this->insert(this->end(), __x);
} void
pop_back() throw()
{ --this->_M_finish; } void
erase(iterator __pos) throw(); void
clear() throw()
{ this->_M_finish = this->_M_start; }
};

   __mini_vector的实现比较简单,内部定义三个指针(_M_start,_M_finish,_M_end_of_storage),分别指向容器即存储数据内存块的首部、数据项尾部和内存块尾部。对外提供了存取的两套接口,push_back()和pop_back()用于向容器后存入和取出数据,insert()和erase()用于向指定位置存取数据。同时重载[]运算符,以支持随机存取。

重点看下insert和erase函数:

template<typename _Tp>
void __mini_vector<_Tp>::
insert(iterator __pos, const_reference __x)
{
// 如果容器还有剩余空间,从后往前,至__pos位置,每个元素后移一个位置,最终把__x插入__pos位置
if (this->_M_space_left())
{
size_type __to_move = this->_M_finish - __pos;
iterator __dest = this->end();
iterator __src = this->end() - 1; ++this->_M_finish;
while (__to_move)
{
*__dest = *__src;
--__dest; --__src; --__to_move;
}
*__pos = __x;
}
else
{
// 如果容器没有空间,重新申请上一次两倍大小的空间最为新的容器,首次使用容器,也分配1个字节的空间大小
size_type __new_size = this->size() ? this->size() * 2 : 1;
iterator __new_start = this->allocate(__new_size);
iterator __first = this->begin();
iterator __start = __new_start;
// 依次将0到__pos-1的位置上的数据迁移到新的内存空间
while (__first != __pos)
{
*__start = *__first;
++__start; ++__first;
}
// 将__x插入到__pos位置
*__start = __x;
++__start; // 将pos+1到end()-1位置的数据迁移到新的内存空间
while (__first != this->end())
{
*__start = *__first;
++__start; ++__first;
}
// 回收旧空间
if (this->_M_start)
this->deallocate(this->_M_start, this->size()); // 更新容器的指针指向新的内存地址
this->_M_start = __new_start;
this->_M_finish = __start;
this->_M_end_of_storage = this->_M_start + __new_size;
}
}
template<typename _Tp>
void __mini_vector<_Tp>::
erase(iterator __pos) throw()
{
// __pos不是最后一个元素,则从__pos+1开始,数据依次往前递进,向前覆盖,最后更新finish指针
while (__pos + 1 != this->end())
{
*__pos = __pos[1];
++__pos;
}
--this->_M_finish;
}

 四、_Inclusive_between

template<typename _Tp>
class _Inclusive_between
{
typedef _Tp pointer;
pointer _M_ptr_value;
typedef typename std::pair<_Tp, _Tp> _Block_pair; public:
_Inclusive_between(pointer __ptr) : _M_ptr_value(__ptr)
{ } bool
operator()(_Block_pair __bp) const throw()
{
if (std::less_equal<pointer>()(_M_ptr_value, __bp.second)
&& std::greater_equal<pointer>()(_M_ptr_value, __bp.first))
return true;
else
return false;
}
};

  _Inclusive_between该类内部主要重载运算符(),来判别指针是否处于__bp的范围内,__bp的类型是std::pair,其数据对也是指针类型(pointer),当_M_ptr_value大于__bp.first且_M_ptr_value小于_bp.second,返回true,否则返回false。该重载主要在内存块回收时,即_M_deallocate_single_object调用下使用,__bp的两个元素分别指向内存链表第一个_Alloc_block和最后一个_Alloc_block,详见后续介绍。

五、_Functor_Ref仿函数

  仿函数,也称为函数对象,一种具有函数特质的对象,通过重载()运算符,使其可以像函数一样被调用。效果类似函数指针,但函数指针不能满足STL对抽象性的要求,也无法与STL其他组件(如配接器adapter)搭配,产生更灵活变化。

template<typename _Functor>
class _Functor_Ref
{
_Functor& _M_fref; public:
typedef typename _Functor::argument_type argument_type;
typedef typename _Functor::result_type result_type; _Functor_Ref(_Functor& __fref) : _M_fref(__fref)
{ } result_type
operator()(argument_type __arg)
{ return _M_fref(__arg); }
};

 六、_Ffit_finder

template<typename _Tp>
class _Ffit_finder
{
typedef std::pair<_Tp, _Tp> _Block_pair;
typedef __detail::__mini_vector<_Block_pair> _BPVector;
typedef typename _BPVector::difference_type _Counter_type; std::size_t* _M_pbitmap;
_Counter_type _M_data_offset; public:
typedef bool result_type;
typedef _Block_pair argument_type; _Ffit_finder() : _M_pbitmap(0), _M_data_offset(0)
{ } bool operator()(_Block_pair __bp) throw()
{
using std::size_t;
// 计算__bp对应内存链表的bitmap个数
_Counter_type __diff = __detail::__num_bitmaps(__bp); // 计算__bp对应内存链表的block个数,同时比对内存链表头部total_num(block使用计数),相等表示内存链表已满,无空闲块可分配
if (*(reinterpret_cast<size_t*>(__bp.first) - (__diff + 1)) == __detail::__num_blocks(__bp))
return false; // __rover 指向bitmap
size_t* __rover = reinterpret_cast<size_t*>(__bp.first) - 1; // 向前遍历bitmap,找到非0的bitmap,非0表示此bitmap对应的内存区域存在未使用的内存块
for (_Counter_type __i = 0; __i < __diff; ++__i)
{
_M_data_offset = __i;
if (*__rover)
{
_M_pbitmap = __rover;
return true;
}
--__rover;
}
return false;
} // 指向存在可用内存块的首个bitmap(从后往前)
std::size_t*
_M_get() const throw()
{ return _M_pbitmap; } // 计算alloc_block的个数,_M_data_offset表示bitmap的数组下标,即_M_data_offset个bitmap映射的内存区域内存块均不可用
_Counter_type
_M_offset() const throw()
{ return _M_data_offset * std::size_t(bits_per_block); }
};

   _Ffit_finder主要根据通过重载()运算符,判断block_pair 对应的内存链表是否还存在空闲的内存块,定位可用内存块对应的bitmap指针和下标;operator ()调用后,_M_pbitmap指向可用内存块的首个bitmap,_M_data_offset 则为可用bitmap的数组下标。这里的内存链表布局,bitmap和alloc_block的增长方向是相反的,bitmap[0]映射block[0]~block[31], bitmap[1]映射block[32]~block[63](32位机器下),如图示:

每个_alloc_block的大小为size_t

enum
{
bits_per_byte = 8,
bits_per_block = sizeof(std::size_t) * std::size_t(bits_per_byte)
};

_Ffit_finder的重载()函数内,调用了两个比较重要的函数,__num_bitmaps和__num_blocks,分别用于计算block_pair对应内存链表的bitmap的个数和block的个数

template<typename _AddrPair>
inline std::size_t __num_blocks(_AddrPair __ap)
{ return (__ap.second - __ap.first) + 1; } template<typename _AddrPair>
inline std::size_t __num_bitmaps(_AddrPair __ap)
{ return __num_blocks(__ap) / std::size_t(bits_per_block); }

 七、_Bitmap_counter

template<typename _Tp>
class _Bitmap_counter
{
typedef typename
__detail::__mini_vector<typename std::pair<_Tp, _Tp> > _BPVector;
typedef typename _BPVector::size_type _Index_type;
typedef _Tp pointer; _BPVector& _M_vbp; // 存放pair<_Tp,_Tp>的向量
std::size_t* _M_curr_bmap; // 指向当前超级块正在使用的bitmap
std::size_t* _M_last_bmap_in_block; // 指向当前超级块的bitmap[]的最后一个bitmap
_Index_type _M_curr_index; // 向量_M_vbp的下标 public:
_Bitmap_counter(_BPVector& Rvbp, long __index = -1) : _M_vbp(Rvbp)
{ this->_M_reset(__index); } void _M_reset(long __index = -1) throw()
{
if (__index == -1)
{
// 初始化
_M_curr_bmap = 0;
_M_curr_index = static_cast<_Index_type>(-1);
return;
}
// 更新index,同时_M_curr_bmap指向对应超级块的首个bitmap,该bitmap紧挨第一个block
_M_curr_index = __index;
_M_curr_bmap = reinterpret_cast<std::size_t*>(_M_vbp[_M_curr_index].first) - 1; // 断言检查,防止数组越界
_GLIBCXX_DEBUG_ASSERT(__index <= (long)_M_vbp.size() - 1); // 计数最后一个bitmap的位置
_M_last_bmap_in_block = _M_curr_bmap - ((_M_vbp[_M_curr_index].second - _M_vbp[_M_curr_index].first + 1) / std::size_t(bits_per_block) - 1);
} // 直接设置_M_curr_bmap,危险的函数,要确保值正确的情况下才能使用
void _M_set_internal_bitmap(std::size_t* __new_internal_marker) throw()
{ _M_curr_bmap = __new_internal_marker; } // _M_curr_bmap == 0表示已无超级块可以使用
bool _M_finished() const throw()
{ return(_M_curr_bmap == 0); } // 重载++运输符的含义,是找到下一个bitmap
_Bitmap_counter& operator++() throw()
{
// 此条件表示当前已经是超级块最后一个bitmap
if (_M_curr_bmap == _M_last_bmap_in_block)
{
// 此条件表示此时已无超级块可用,当前超级块是最后一个
if (++_M_curr_index == _M_vbp.size())
_M_curr_bmap = 0;
else
// 使用下一个超级块,几个指针重新初始化
this->_M_reset(_M_curr_index);
}
else
// 继续使用当前超级块,_M_curr_bmap指针前移,指向下一个bitmap
--_M_curr_bmap;
return *this;
} // 指向当前超级块正在使用的bitmap
std::size_t* _M_get() const throw()
{ return _M_curr_bmap; } // 指向当前超级块的首个block
pointer _M_base() const throw()
{ return _M_vbp[_M_curr_index].first; } // 计算首个block与当前bitmap的偏移,bit单位,该计算结果也表示,_M_curr_bmap前已分配使用的block个数
_Index_type _M_offset() const throw()
{
return std::size_t(bits_per_block) * ((reinterpret_cast<std::size_t*>(this->_M_base()) - _M_curr_bmap) - 1);
} // 获取当前pair<_Tp,_Tp>的下标,pair<_Tp,_Tp>对应着当前的超级块的首尾block
  _Index_type _M_where() const throw() { return _M_curr_index; } };

  _Bitmap_counter作为位图的管理者,用来实现位图的搜索定位,同时也搜索定位block_pair。 _Bitmap_counter的成员_BPVector存储着每个超级块(即内存链表)的首尾两个_alloc_block的地址,通过模板参数_Tp传入。_Bitmap_counter的重点在于operator++,其用来在内存池(即多个内存链表)之间递增bitmap指针,同时,operator++调用后,亦可通过_M_where()定位当前bitmap对应的block_pair,通过_M_offset()计算使用的block个数。

  _M_offset表示的意义可通过如下图示直观呈现

八、free_list

class free_list
{
public:
typedef std::size_t* value_type;
typedef __detail::__mini_vector<value_type> vector_type;
typedef vector_type::iterator iterator;
typedef __mutex __mutex_type; private:
struct _LT_pointer_compare
{
bool operator()(const std::size_t* __pui, const std::size_t __cui) const throw()
{ return *__pui < __cui; }
}; #if defined __GTHREADS
__mutex_type& _M_get_mutex()
{
static __mutex_type _S_mutex;
return _S_mutex;
}
#endif
// 获取内存池
vector_type& _M_get_free_list()
{
static vector_type _S_free_list;
return _S_free_list;
} // 内存池最大存储64条内存链表,即超级块,且按其大小升序排列,当内存池满时,新的超级块请求入内存池,
// 判断超级块的total_size大小(__addr指向total_size),若为最大,不回收到内存池,直接还给操作系统,
// 否则将内存池最大的超级块还给操作系统,再将该超级块插入内存池
void _M_validate(std::size_t* __addr) throw()
{
vector_type& __free_list = _M_get_free_list();
const vector_type::size_type __max_size = 64;
if (__free_list.size() >= __max_size)
{
if (*__addr >= *__free_list.back())
{
::operator delete(static_cast<void*>(__addr));
return;
}
else
{
::operator delete(static_cast<void*>(__free_list.back()));
__free_list.pop_back();
}
} iterator __temp = __detail::__lower_bound(__free_list.begin(), __free_list.end(), *__addr, _LT_pointer_compare()); __free_list.insert(__temp, __addr);
} // 决定当前内存请求的内存损耗是否可接受,这里定的是36%,超过返回false,否则返回true
bool _M_should_i_give(std::size_t __block_size, std::size_t __required_size) throw()
{
const std::size_t __max_wastage_percentage = 36;
if (__block_size >= __required_size && (((__block_size - __required_size) * 100 / __block_size) < __max_wastage_percentage))
return true;
else
return false;
} public: // 超级块回收到内存池,_addr指向total_num,记录超级块中block的使用计数
inline void _M_insert(std::size_t* __addr) throw()
{
#if defined __GTHREADS
__scoped_lock __bfl_lock(_M_get_mutex());
#endif
// _M_validate参数的指针需指向超级块的total_size字段(内存池根据此值排序超级块),因此这里指针要往前偏移一个size_t
this->_M_validate(reinterpret_cast<std::size_t*>(__addr) - 1);
} // 根据申请内存大小(__sz)返回可使用的超级块
std::size_t* _M_get(std::size_t __sz) _GLIBCXX_THROW(std::bad_alloc); // 清空内存池
void _M_clear();
};

  free_list,即开篇所说的内存池,管理着不超过64条空闲链表(本质是内存空间连续的超级块)的申请和回收,它是bitmap_allocator空间分配器中系统内存申请和释放的直接接口对象。free_list内存池,内部通过使用__mini_vector容器,存储每个超级块的首地址。同时,超级块的首地址指向的size_t字段,记录着超级块除第一个size_t字段外的总内存大小,这个大小,也是空间配置器请求的块大小。内存池根据请求的大小,额外增加了一个size_t,用于记录该值,并以该值,作为__mini_vector容器元素排列的依据。

  内存池free_list提供了_M_insert()和_M_get()接口,前者用于将超级块回收,后者用于申请超级块。超级块的回收,可能回收到内存池,也可能直接释放给操作系统,具体根据内存池中现存超级块的个数以及当前超级块的大小。当内存池满时,若超级块大小超过内存池中最大的超级块,则直接调用operator delete释放给操作系统,否则,将内存池中最大的内存块释放给操作系统,再将其按序插入内存池。其具体行为在_M_validate()接口内实现。

  _M_validate()接口内调用的函数__detail::__lower_bound(),__detail是命名空间,前面几个章节所述的组件也均定义在其中。函数__lower_bound()用于定位边界,根据自定义的比较函数,和指定的迭代器范围,定位__val的右边界,返回值最终指向比__val大的第一个边界。具体实现如下:

template<typename _ForwardIterator, typename _Tp, typename _Compare>
_ForwardIterator __lower_bound(_ForwardIterator __first, _ForwardIterator __last, const _Tp& __val, _Compare __comp)
{
typedef typename __mv_iter_traits<_ForwardIterator>::difference_type _DistanceType; _DistanceType __len = __last - __first;
_DistanceType __half;
_ForwardIterator __middle; // 折半查找
while (__len > 0)
{
__half = __len >> 1;
__middle = __first;
__middle += __half;
if (__comp(*__middle, __val))
{
__first = __middle;
++__first;
__len = __len - __half - 1;
}
else
__len = __half;
}
return __first;
}

_M_get()的实现如下:

size_t* free_list::_M_get(size_t __sz) throw(std::bad_alloc)
{
#if defined __GTHREADS
// 内存池是全局静态唯一的,多线程访问需加锁
__mutex_type& __bfl_mutex = _M_get_mutex();
__bfl_mutex.lock();
#endif
const vector_type& __free_list = _M_get_free_list();
using __gnu_cxx::__detail::__lower_bound;
iterator __tmp = __lower_bound(__free_list.begin(), __free_list.end(), __sz, _LT_pointer_compare()); // 内存池内找不到合适的超级块(1、申请大小比内存池中的超级块都大,2、现存的超级块由于内部碎片问题,不宜分出),向操作系统申请
if (__tmp == __free_list.end() || !_M_should_i_give(**__tmp, __sz))
{
// 这里可以释放锁,因为operator new是线程安全的
#if defined __GTHREADS
__bfl_mutex.unlock();
#endif // 尝试两次申请内存,当第一次失败时,清空内存池,使内存池内存资源归还系统,再尝试获取
int __ctr = 2;
while (__ctr)
{
size_t* __ret = 0;
--__ctr;
__try
{
// 这里多申请了一个size_t的内存,用来记录__sz
__ret = reinterpret_cast<size_t*>(::operator new(__sz + sizeof(size_t)));
}
__catch(const std::bad_alloc&)
{
this->_M_clear();
}
if (!__ret)
continue;
*__ret = __sz;
// 多申请的size_t不返回给分配器,只对内存池可见,分配器不需关注该值
return __ret + 1;
}
std::__throw_bad_alloc();
}
else
{
// 内存池中能找到符合条件的超级块,则从内存池中取出
size_t* __ret = *__tmp;
_M_get_free_list().erase(__tmp);
#if defined __GTHREADS
__bfl_mutex.unlock();
#endif
return __ret + 1;
}
}

  _M_get()用于申请超级块,首先根据申请的大小,于内存池中尝试查找适宜的超级块,若内存池中已有的超级块均小于申请的超级块大小,或者内存池内的超级块内存浪费率超过36%,认为内部碎片化过大不宜分出,则向操作系统申请内存已返回给空间分配器。否则,将从内存池中移出适宜的超级块用以返回。_M_should_i_give()函数定义了内存过度浪费的评定标准(36%),当未使用的内存超过超级块的36%,认为分出该超级块过于浪费,内部碎片化严重,不宜分出。

  _M_clear()用于清空内存池,将所有超级块释放回操作系统,实现如下:

void free_list::_M_clear()
{
#if defined __GTHREADS
__gnu_cxx::__scoped_lock __bfl_lock(_M_get_mutex());
#endif
vector_type& __free_list = _M_get_free_list();
iterator __iter = __free_list.begin();
while (__iter != __free_list.end())
{
::operator delete((void*)*__iter);
++__iter;
}
__free_list.clear();
}

九、bitmap_allocator

  介绍完上面空间配置器的所有组件,可以来看下空间配置器的具体实现了,重点还是在于allocate()和deallocate()的实现

// 前向声明
template<typename _Tp>
class bitmap_allocator; // void的特化实现
template<>
class bitmap_allocator<void>
{
public:
typedef void* pointer;
typedef const void* const_pointer; typedef void value_type;
template<typename _Tp1>
struct rebind
{
typedef bitmap_allocator<_Tp1> other;
};
};
template<typename _Tp>
class bitmap_allocator : private free_list
{
public:
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef _Tp* pointer;
typedef const _Tp* const_pointer;
typedef _Tp& reference;
typedef const _Tp& const_reference;
typedef _Tp value_type;
typedef free_list::__mutex_type __mutex_type; template<typename _Tp1>
struct rebind
{
typedef bitmap_allocator<_Tp1> other;
}; #if __cplusplus >= 201103L
typedef std::true_type propagate_on_container_move_assignment;
#endif private:
template<std::size_t _BSize, std::size_t _AlignSize>
struct aligned_size
{
enum
{
// value为_BSize以_AlignSize对齐后的大小
modulus = _BSize % _AlignSize,
value = _BSize + (modulus ? _AlignSize - (modulus) : 0)
};
}; // 空间分配器分配的单元,_BALLOC_ALIGN_BYTES是宏定义,定义为8
struct _Alloc_block
{
char __M_unused[aligned_size<sizeof(value_type), _BALLOC_ALIGN_BYTES>::value];
}; typedef typename std::pair<_Alloc_block*, _Alloc_block*> _Block_pair; // first指向超级块的首个block,second指向超级块最后一个block
typedef typename __detail::__mini_vector<_Block_pair> _BPVector;
typedef typename _BPVector::iterator _BPiter; // 模板参数_Predicate为仿函数,_S_find主要寻找可用的超级块对应的_Block_pair
template<typename _Predicate>
static _BPiter _S_find(_Predicate __p)
{
_BPiter __first = _S_mem_blocks.begin();
while (__first != _S_mem_blocks.end() && !__p(*__first))
++__first;
return __first;
} #if defined _GLIBCXX_DEBUG
void _S_check_for_free_blocks() throw()
{
typedef typename __detail::_Ffit_finder<_Alloc_block*> _FFF;
_BPiter __bpi = _S_find(_FFF());
_GLIBCXX_DEBUG_ASSERT(__bpi == _S_mem_blocks.end());
}
#endif // 复杂度:O(1),但内部取决于free_list::M_get。写入位图头的部分时间复杂度O(X),其中X是获取到的超级块中block的个数。
void _S_refill_pool() _GLIBCXX_THROW(std::bad_alloc); static _BPVector _S_mem_blocks; // 存放空间分配器可用的超级块,以block_pair形式保存
static std::size_t _S_block_size;  // 超级块中block的个数,每次从内存池申请超级块则加倍,回收减一半
static __detail::_Bitmap_counter<_Alloc_block*> _S_last_request; // 指向上一次访问的位图
static typename _BPVector::size_type _S_last_dealloc_index; // 上一次回收block的超级块对应在_S_mem_blocks的下标
#if defined __GTHREADS
static __mutex_type _S_mut;
#endif public:
// 分配和回收函数
pointer _M_allocate_single_object() _GLIBCXX_THROW(std::bad_alloc);
void _M_deallocate_single_object(pointer __p) throw(); public:
bitmap_allocator() _GLIBCXX_USE_NOEXCEPT
{ } bitmap_allocator(const bitmap_allocator&) _GLIBCXX_USE_NOEXCEPT
{ } template<typename _Tp1>
bitmap_allocator(const bitmap_allocator<_Tp1>&) _GLIBCXX_USE_NOEXCEPT
{ } ~bitmap_allocator() _GLIBCXX_USE_NOEXCEPT
{ } _GLIBCXX_NODISCARD pointer allocate(size_type __n); _GLIBCXX_NODISCARD pointer
allocate(size_type __n, typename bitmap_allocator<void>::const_pointer)
{ return allocate(__n); } void deallocate(pointer __p, size_type __n) throw(); pointer
address(reference __r) const _GLIBCXX_NOEXCEPT
{ return std::__addressof(__r); } const_pointer
address(const_reference __r) const _GLIBCXX_NOEXCEPT
{ return std::__addressof(__r); } size_type
max_size() const _GLIBCXX_USE_NOEXCEPT
{ return size_type(-1) / sizeof(value_type); } #if __cplusplus >= 201103L
template<typename _Up, typename... _Args>
void
construct(_Up* __p, _Args&&... __args)
{ ::new((void *)__p) _Up(std::forward<_Args>(__args)...); } template<typename _Up>
void destroy(_Up* __p)
{ __p->~_Up(); }
#else
void construct(pointer __p, const_reference __data)
{ ::new((void *)__p) value_type(__data); } void destroy(pointer __p)
{ __p->~value_type(); }
#endif
};

  bitmap_allocator继承自free_list。free_list主要为bitmap_allocator提供了超级块的申请和回收接口,负责向操作系统直接申请和释放内存资源。

  bitmap_allocator内部定义了几个成员变量,_S_mem_blocks主要存储向内存池free_list申请的超级块,其存储方式为block_pair方式,pair的两个键为指针,分别指向超级块的首尾两个block。上层容器通过bitmap_allocator申请的内存,会先从_S_mem_blocks中找到合适的超级块,再在超级块中找到合适的block。

  bitmap_allocator定义的construct()和destroy()函数,和其他空间配置器的实现一致,采用placement new进行构造,采用显示析构方式析构对象。

  bitmap_allocator的分配和回收仅适合单个对象,当试图申请或回收多个对象时,采用operator new和operator delete进行分配和回收。单个对象的分配和回收在接口_M_allocate_single_object()和_M_deallocate_single_object()实现。

_S_mem_blocks的示意图如下:

  

allocate

_GLIBCXX_NODISCARD pointer allocate(size_type __n)
{
// 申请对象个数超过限制,抛出异常
if (__n > this->max_size())
std::__throw_bad_alloc(); #if __cpp_aligned_new
if (alignof(value_type) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
// 当类型对齐后的大小大于系统默认内存对齐大小,采用 void* operator new ( std::size_t count, std::align_val_t al) 作为内存申请的接口
const size_type __b = __n * sizeof(value_type);
std::align_val_t __al = std::align_val_t(alignof(value_type));
return static_cast<pointer>(::operator new(__b, __al));
}
#endif if (__builtin_expect(__n == 1, true))
return this->_M_allocate_single_object();
else
{
// 超过1个对象的申请,用operator new
const size_type __b = __n * sizeof(value_type);
return reinterpret_cast<pointer>(::operator new(__b));
}
}
pointer _M_allocate_single_object() _GLIBCXX_THROW(std::bad_alloc)
{
using std::size_t;
#if defined __GTHREADS
__scoped_lock __bit_lock(_S_mut);
#endif // _要点1,S_last_request是_Bitmap_counter类型,指向上次访问的位图,这里递增找到下一个位图
while (_S_last_request._M_finished() == false && (*(_S_last_request._M_get()) == 0))
_S_last_request.operator++(); // 要点2,如果找不到位图
if (__builtin_expect(_S_last_request._M_finished() == true, false))
{
// 要点3,寻找下一个可用超级块
typedef typename __detail::_Ffit_finder<_Alloc_block*> _FFF;
_FFF __fff;
_BPiter __bpi = _S_find(__detail::_Functor_Ref<_FFF>(__fff)); if (__bpi != _S_mem_blocks.end())
{
// 要点4,位图扫描,并将位图最低位起第一个非0的位设为1,标记使用该block
size_t __nz_bit = _Bit_scan_forward(*__fff._M_get());
__detail::__bit_allocate(__fff._M_get(), __nz_bit); // 要点5,_S_last_request复位,重新定位到可用的超级块和位图
_S_last_request._M_reset(__bpi - _S_mem_blocks.begin()); // 要点6,定位到block
pointer __ret = reinterpret_cast<pointer>(__bpi->first + __fff._M_offset() + __nz_bit);
size_t* __puse_count = reinterpret_cast<size_t*>(__bpi->first) - (__detail::__num_bitmaps(*__bpi) + 1); // 使用计数字段加1
++(*__puse_count);
return __ret;
}
else
{
// 要点7,_S_mem_blocks找不到可用超级块,则从内存池中申请超级块,并复位_S_last_request
_S_refill_pool();
_S_last_request._M_reset(_S_mem_blocks.size() - 1);
}
} // 到这里是可定有超级块可用的了,从超级块中取出block返回给上层容器
size_t __nz_bit = _Bit_scan_forward(*_S_last_request._M_get());
__detail::__bit_allocate(_S_last_request._M_get(), __nz_bit); pointer __ret = reinterpret_cast<pointer>(_S_last_request._M_base() + _S_last_request._M_offset() + __nz_bit); size_t* __puse_count = reinterpret_cast<size_t*>(_S_mem_blocks[_S_last_request._M_where()].first) -
(__detail::__num_bitmaps(_S_mem_blocks[_S_last_request._M_where()]) + 1); ++(*__puse_count);
return __ret;
}

  根据上述_M_allocate_single_object()代码里的注释要点,具体说明:

要点1:S_last_request是_Bitmap_counter类型,用于定位上次使用的位图和超级块,具体可见第七节_Bitmap_counter的介绍。_M_finished()接口用于判断是否遍历结束,内部判断_M_curr_bmap == 0条件是否成立。该条件成立的情况有两种,一是初始化阶段,容器内无超级块,二是容器内有超级块,但随着operator++,已经访问到容器末端的超级块和位图,最后一次operator++操作后会将_M_curr_bmap 置0。_Bitmap_counter只重载了operator++,其只能往后遍历超级块。所以_M_finished()==false表示还未遍历完容器内的超级块。_M_get()接口返回指向位图的指针,所以条件*(_S_last_request._M_get()) == 0表示位图映射的区域的block已经都分配使用了。注释要点1的代码,主要是循环遍历位图,直至找到存在未分配block的超级块,以及定位该超级块中首个映射区域内存在未分配block的位图;

要点2:经过要点1的遍历,无法找到可用超级块;

要点3:要点1的遍历,是从上一次访问的位图开始往后遍历,此时找不到可用超级块,则需从头开始遍历。_S_find()主要寻找可用的超级块对应的_Block_pair,它遍历了_S_mem_blocks内所有的超级块,并判断超级块中是否存在未分配的block(语句!__p(*__first),是仿函数_Ffit_finder的operator()的调用);

要点4:_Bit_scan_forward(),扫描位图,并返回位图低位连续为0的位数__nz_bit ,表示该位图映射的区域起始,至少有__nz_bit个已分配出去的block。 __fff._M_get()返回指向位图的指针,该位图是经由_S_find()定位。__bit_allocate()将位图中指定的某位设置为0,标记该位对应的block已使用。几个函数的实现如下:

inline void __bit_allocate(std::size_t* __pbmap, std::size_t __pos) throw()
{
std::size_t __mask = 1 << __pos;
__mask = ~__mask;
*__pbmap &= __mask;
} inline std::size_t _Bit_scan_forward(std::size_t __num)
{ return static_cast<std::size_t>(__builtin_ctzl(__num)); }

要点5:前面经由_S_find()搜索到超级块后,需要将S_last_request重新复位使其定位到当前的超级块,同时,又将位图指针定位到该超级块的首个位图,该位图与前面_S_find()定位的位图可能不是同一个,但没关系,下次调用_M_allocate_single_object()分配,还会再次经过要点1的代码遍历位图;

要点6:__fff._M_offset()计算起始位图到当前位图的位数,见第七节最后图示,__nz_bit则为当前位图中低位连续0的位数。_bpi->first + __fff._M_offset() + __nz_bit便定位到了当前分配的block的位置;

要点7:_S_mem_blocks找不到可用超级块,则从内存池中申请超级块,重新填充_S_mem_blocks。_S_refill_pool()具体实现如下:

// 复杂度:O(1),但内部取决于free_list::M_get。写入位图头的部分时间复杂度O(X),其中X是获取到的超级块中block的个数。
void _S_refill_pool() _GLIBCXX_THROW(std::bad_alloc)
{
using std::size_t;
#if defined _GLIBCXX_DEBUG
_S_check_for_free_blocks();
#endif // 计算bitmap的个数和总的超级块大小
const size_t __num_bitmaps = (_S_block_size / size_t(__detail::bits_per_block));
const size_t __size_to_allocate = sizeof(size_t) + _S_block_size * sizeof(_Alloc_block) + __num_bitmaps * sizeof(size_t); // 向free_list申请超级块
size_t* __temp = reinterpret_cast<size_t*>(this->_M_get(__size_to_allocate));
*__temp = 0; // 初始使用计数设为0
++__temp; _Block_pair __bp = std::make_pair(reinterpret_cast<_Alloc_block*>(__temp + __num_bitmaps),
reinterpret_cast<_Alloc_block*>(__temp + __num_bitmaps) + _S_block_size - 1); _S_mem_blocks.push_back(__bp); for (size_t __i = 0; __i < __num_bitmaps; ++__i)
__temp[__i] = ~static_cast<size_t>(0); // 1 表示空闲. // 下一次分配为此次的两倍大小
_S_block_size *= 2;
}

 deallocate

void deallocate(pointer __p, size_type __n) throw()
{
if (__builtin_expect(__p != 0, true))
{
#if __cpp_aligned_new
if (alignof(value_type) > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
{
// 当类型对齐后的大小大于系统默认内存对齐大小,采用 void* operator delete () 作为内存释放的接口
::operator delete(__p, std::align_val_t(alignof(value_type)));
return;
}
#endif
// 超过1个对象的释放,用operator delete
if (__builtin_expect(__n == 1, true))
this->_M_deallocate_single_object(__p);
else
::operator delete(__p);
}
}
void _M_deallocate_single_object(pointer __p) throw()
{
using std::size_t;
#if defined __GTHREADS
__scoped_lock __bit_lock(_S_mut);
#endif
_Alloc_block* __real_p = reinterpret_cast<_Alloc_block*>(__p); typedef typename _BPVector::iterator _Iterator;
typedef typename _BPVector::difference_type _Difference_type; _Difference_type __diff;
long __displacement; _GLIBCXX_DEBUG_ASSERT(_S_last_dealloc_index >= 0); // 要点1,查找释放的block在哪个超级块中,先从上次回收的超级块查找。
// __diff为_S_mem_blocks中该超级块的下标,__displacement为block在该超级块的下标
// _S_last_dealloc_index为_S_mem_blocks中该超级块的下标
__detail::_Inclusive_between<_Alloc_block*> __ibt(__real_p);
if (__ibt(_S_mem_blocks[_S_last_dealloc_index]))
{
// 要点2,__real_p所指向的block位于超级块_S_mem_blocks[_S_last_dealloc_index]中
_GLIBCXX_DEBUG_ASSERT(_S_last_dealloc_index <= _S_mem_blocks.size() - 1); __diff = _S_last_dealloc_index;
__displacement = __real_p - _S_mem_blocks[__diff].first;
}
else
{
// 要点3,从头开始查找超级块
_Iterator _iter = _S_find(__ibt); _GLIBCXX_DEBUG_ASSERT(_iter != _S_mem_blocks.end()); __diff = _iter - _S_mem_blocks.begin();
__displacement = __real_p - _S_mem_blocks[__diff].first;
_S_last_dealloc_index = __diff;
} // __bitmapC指向该block映射的位图,__rotate为该block在该位图中映射的位
const size_t __rotate = (__displacement % size_t(__detail::bits_per_block));
size_t* __bitmapC = reinterpret_cast<size_t*>(_S_mem_blocks[__diff].first) - 1;
__bitmapC -= (__displacement / size_t(__detail::bits_per_block)); // 标记该位为1,表示未使用
__detail::__bit_free(__bitmapC, __rotate);
size_t* __puse_count = reinterpret_cast<size_t*>(_S_mem_blocks[__diff].first) - (__detail::__num_bitmaps(_S_mem_blocks[__diff]) + 1); _GLIBCXX_DEBUG_ASSERT(*__puse_count != 0); // total_num字段,递减block使用计数
--(*__puse_count); if (__builtin_expect(*__puse_count == 0, false))
{
// 要点4,使用计数为0,表示超级块中所有block都回收了,_S_block_size减半,同时将超级块回收回内存池,
_S_block_size /= 2;
this->_M_insert(__puse_count);
_S_mem_blocks.erase(_S_mem_blocks.begin() + __diff); // 上次请求的超级块比当前回收的超级块序号大,则需复位_S_last_request,
// 因为随着超级块从_S_mem_blocks中删除,_S_last_request内的超级块下标已变化。
if ((_Difference_type)_S_last_request._M_where() >= __diff--)
_S_last_request._M_reset(__diff); // 回收的超级块之前是最后一块,则更新_S_last_dealloc_index为当前_S_mem_blocks的最后一个超级块的下标
if (_S_last_dealloc_index >= _S_mem_blocks.size())
{
_S_last_dealloc_index =(__diff != -1 ? __diff : 0);
_GLIBCXX_DEBUG_ASSERT(_S_last_dealloc_index >= 0);
}
}
}

  根据上述_M_deallocate_single_object()代码里的注释要点,具体说明:

要点1:_Inclusive_between亦是仿函数,其重载的operator()主要判断block在不在指定的block_pair区间内,也即block是不是属于block_pair对应的超级块中,具体实现可见第四节_Inclusive_between介绍。要点1的代码可见,是先从上次回收的超级块开始判断的,_S_last_dealloc_index 是超级块在_S_mem_blocks容器中的下标。

要点2:先后两次回收的block处于同一超级块的概率更高点,所以这里先从上次回收的超级块开始判断,应该是出于性能考虑,避免每次回收都遍历_S_mem_blocks。

要点3:当前回收的block与上次回收的不属于同一超级块,此时需要遍历_S_mem_blocks查找超级块了,通过_S_find()定位。最终要更新_S_last_dealloc_index 。

要点4:当超级块中所有block都回收完,此时考虑将该超级块从容器_S_mem_blocks中移出,并回收到内存池free_list。每次回收超级块到内存池,_S_block_size 都要减半,与之前面对应,每次向内存池free_list申请超级块,_S_block_size 都要加倍。通过这种控制方式,来调控向操作系统申请内存和释放内存的频率。

_S_block_size 是静态成员,初始定义为64(32位机器下),即第一个超级块的block个数为64,位图bitmap个数为2。bits_per_block为32,见第六节_Ffit_finder描述

template<typename _Tp>
std::size_t bitmap_allocator<_Tp>::_S_block_size = 2 * std::size_t(__detail::bits_per_block);

 __detail::__bit_free() 将位图指定位置1,标记为未使用,实现如下:

inline void __bit_free(std::size_t* __pbmap, std::size_t __pos) throw()
{
std::size_t __mask = 1 << __pos;
*__pbmap |= __mask;
}

十、总结

  bitmap_allocator申请内存,是先从上一次分配block的超级块中,往后查找未使用的block分配,若超级块中block都已分配,则从_S_mem_blocks容器内往后查找下一个超级块。如果_S_mem_blocks容器内没有超级块了,则从内存池free_list中申请超级块,并将其缓存到S_mem_blocks容器内,以待后续分配使用。_S_mem_blocks容器内的超级块没有按大小升序排列,而内存池free_list中的超级块是按大小升序排列的。在向内存池free_list申请超级块时,有可能从内存池中取,也有可能向操作系统重新申请,具体依赖于申请超级块的大小。

  bitmap_allocator释放内存,会判断释放的block是否与上次释放的同属一个超级块,若是则直接将该超级块的block对应的位图置位为1,标记未使用。否则,从_S_mem_blocks容器中查找block所属的超级块,再进行位图置位处理。需要注意的是,当超级块block已全部回收时,需要将超级块从_S_mem_blocks容器中删除,回收到内存池。回收到内存池也分两种情况,可能插入按序到内存池中,也可能直接释放给操作系统,具体依赖于回收的超级块大小。

STL空间配置器源码分析(四)bitmap_allocator的更多相关文章

  1. 【陪你系列】5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub https://github.com/rongweihe/MoreT ...

  2. 咬碎STL空间配置器

    STL空间配置器 一.开场白: 给我的感觉就是,了解是空间配置器的功能,是那么的明了:在看原理,我还是很开心:接下来是360度大转变: 那么长的变量或者函数命名.那么多的宏.不爽,不过,遇上我这种二货 ...

  3. 使用react全家桶制作博客后台管理系统 网站PWA升级 移动端常见问题处理 循序渐进学.Net Core Web Api开发系列【4】:前端访问WebApi [Abp 源码分析]四、模块配置 [Abp 源码分析]三、依赖注入

    使用react全家桶制作博客后台管理系统   前面的话 笔者在做一个完整的博客上线项目,包括前台.后台.后端接口和服务器配置.本文将详细介绍使用react全家桶制作的博客后台管理系统 概述 该项目是基 ...

  4. linux调度器源码分析 - 运行(四)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 之前的文章已经将调度器的数据结构.初始化.加入进程都进行了分析,这篇文章将主要说明调度器是如何在程序稳定运 ...

  5. stl空间配置器线程安全问题补充

    摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关 ...

  6. 【转】STL空间配置器

    STL空间配置器(allocator)在所有容器内部默默工作,负责空间的配置和回收.STL标准为空间配置器定义了标准接口(可见<STL源码剖析>P43).而具体实现细节则由各编译器实现版本 ...

  7. STL空间配置器

    1.什么是空间配置器? 空间配置器负责空间配置与管理.配置器是一个实现了动态空间配置.空间管理.空间释放的class template.以内存池方式实现小块内存管理分配.关于内存池概念可以点击:内存池 ...

  8. 一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  9. 一步步实现windows版ijkplayer系列文章之二——Ijkplayer播放器源码分析之音视频输出——视频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

随机推荐

  1. java对配置文件properties的操作

    1.读取配置文件的键值对,转为Properties对象:将Properties(键值对)对象写入到指定文件. package com.ricoh.rapp.ezcx.admintoolweb.util ...

  2. sublime settings

    { "font_face": "Monaco", // 编辑器的字体 "font_size": 13, // 字号 "highli ...

  3. ctf之GET

    题目信息如图 启动环境 根据信息只需将参数?what=flag添加到url上即可

  4. winform 代码生成textbox ,checkbox

    参考地址:https://jingyan.baidu.com/article/380abd0a6b80701d90192cde.html 首先搭建好Winform项目框架后,创建窗体页面后自行布局 这 ...

  5. 七天接手react项目 系列 —— 尾篇(antd 和 mobx)

    其他章节请看: 七天接手react项目 系列 尾篇 前面我们依次学习了 react 基础知识.react 脚手架创建项目.react 路由,已经花费了不少时间,但距离接手 spug_web 项目还有一 ...

  6. 生产出现oom问题,怎么排查?

    生产出现oom问题,怎么排查?   1.使用dmesg命令查看系统日志 dmesg |grep -E 'kill|oom|out of memory',可以查看操作系统启动后的系统日志,这里就是查看跟 ...

  7. Hadoop全分布式

    1.安装jdk      Linux下安装jdk-7u67-linux-x64.rpm 2.免密登录   ssl免密登录(centos6) 3.同步时间:date -s "2020-04-0 ...

  8. mac 添加java_home 和启动es

    转:https://www.cnblogs.com/wxmdevelop/p/9935797.html p.p1 { margin: 0; font: 11px Menlo; color: rgba( ...

  9. 如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?

    可以在@RequestMapping注解里面加上method=RequestMethod.GET.

  10. Redis 相比 Memcached 有哪些优势?

    1.Memcached 所有的值均是简单的字符串,redis 作为其替代者,支持更为丰 富的数据类 2.Redis 的速度比 Memcached 快很 3.Redis 可以持久化其数据