STL空间分配器源码分析(四)bitmap_allocator

1. 摘要

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

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

img

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+(42)+(464)= 268,_Alloc_block初始为64个,分配个数视情况而定,每次新申请内存链表则2倍递增,回收入内存池则减半(详见后面代码分析)。

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:真正的空间分配器,依赖上述各个组件实现内存单元的申请和释放;

3. __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;
}

4. _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,详见后续介绍。

5. _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); }
};

6. _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位机器下),如图示:

img

每个_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); }

7. _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表示的意义可通过如下图示直观呈现

img

8. 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();
}

9. 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的示意图如下:

img

9.1 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;
}

9.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;
}

10. 总结

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容器中删除,回收到内存池。回收到内存池也分两种情况,可能插入按序到内存池中,也可能直接释放给操作系统,具体依赖于回收的超级块大小。

posted @ 2022-04-09 15:00  流翎  阅读(350)  评论(0编辑  收藏  举报