一、摘要

  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. 做一个能对标阿里云的前端APM工具(上)

    APM 全称是 Application Performance Monitor,即性能监控 这篇文章有三个前提: 从产品形态上看这肯定不是一个能够媲美阿里产品的竞品,所以抱歉我碰瓷了.你可以把这里的阿 ...

  2. 12.16 JAVA swing

    ------------恢复内容开始------------ 12.16JAVA swing 1.框架 JFrame>JPanel>组件JButton JTestfilled JTable ...

  3. C#中的类型转换-自定义隐式转换和显式转换

    目录 前言 基础知识 示例代码 实际应用 问题 答案 报错 用户定义的转换必须是转换成封闭类型,或者从封闭类型转换 参考 其他 应用和设计 读音 参考 前言 有时我们会遇到这么一种情况:在json数据 ...

  4. 发现程序美----while+for冒泡实现的

    思想记录: 每一轮回的冒泡都将产生一个最大值,其后每次循环次数都将少一次,因为每次都会确定一个最大值. private void method(){ int[] list = {10,7,8,4,7, ...

  5. 是否使用过 Redis 集群,集群的原理是什么?

    1).Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务. 2).Redis Cluster 着眼于扩展性,在单个 redis ...

  6. spring JDBC API 中存在哪些类?

    JdbcTemplate SimpleJdbcTemplate NamedParameterJdbcTemplate SimpleJdbcInsert SimpleJdbcCall

  7. 关于 OOP 和设计模式?

    这部分包含 Java 面试过程中关于 SOLID 的设计原则,OOP 基础,如类,对象, 接口,继承,多态,封装,抽象以及更高级的一些概念,如组合.聚合及关联. 也包含了 GOF 设计模式的问题.

  8. 请用c++ 实现stl中的string类,实现构造,拷贝构造,析构,赋值,比较,字符串相加,获取长度及子串等功能。

    1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 class String{ 5 public ...

  9. vue 初识(基础语法与数据驱动模型)

    1.es6的语法 let 特点: 1.局部作用域 2.不会存在变量提升 3.变量不能重复声明 const 特点: 1.局部作用域 2.不会存在变量提升 3.不能重复声明,只声明常量 不可变的量 模板字 ...

  10. mysql8.0时区问题

    今天在mysql新增一条数据的时候,发现时间类型的字段比起现在少了8个小时,查了资料才发现,这个是MySQL8.0出现的问题,讲下解决方法. 1.在java项目中application.yml文件中的 ...