STL中一级、二级空间配置器原理分析及实现

考虑到一些容器,如vector、list,会存在一些问题:

(1)效率低。动态在堆上开辟一块空间,需要在堆上找合适的内存块。
(2)存在内存碎片。导致需要一块较大的空间时,需要在堆上找合适的内存块找不到。
(3)开销太大。为了管理malloc的空闲空间,每一个独立块的最前面都包含了一个“头部信息”,属于额外开销。
(4)存在内存泄露。
(5)代码复用率低。每个容器都会使用相同的new一块空间。
(6)没有空间不足的应对措施。
针对以上问题,提出了空间配置器,可以帮助我们来管理空间,将容器中出现的问题考虑进去。

空间配置器

空间配置器,就是用来配置、管理和释放空间的,给所有的容器包括算法提供生存空间。

作用:

(1)提高代码复用率,功能模块化。
(2)减少内存碎片问题。
(3)提高内存分配的效率。
(4)有内存不足时的应对措施。
(5)隐藏实际中对存储空间的分配及释放细节,确保所有被分配的存储空间都最终获得释放。
(5)考虑多线程状态。

考虑到小型区块可能导致的内存碎片问题,设置了两级空间配置器。分别为:一级空间配置器、二级空间配置器。当区块大于128字节,调用一级空间配置器;小于等于128字节,为了降低额外开销,用底层较复杂的二级空间配置器。

一级空间配置器

用malloc()、free()、realloc()等C函数执行内存配置、释放、重配置操作,并实现出类似的C++new_hanle的机制

//一级空间配置器
//> 128
typedef void (*PMallocHandler)();//是一个类型,函数指针
template <int inst>
class MallocAllocTemplate{
public:
     static void* Allocate(size_t n)
     {
          void *result = malloc(n);
          if (NULL == result)
              result = OOM_Malloc(n);//申请空间失败的应急措施
          __TRACE_DEBUG("一级:%d\n", n);
          return result;
     }
     static void Deallocate(void* p, size_t)
     {
          __TRACE_DEBUG("一级释放\n");
          free(p);
     }
     static void* Reallocate(void* p,size_t,size_t newSize)
     {
          void *result = realloc(p, newSize);
          if (0 == result)
              result = OOM_Realloc(p, newSize);
          return result;
     }
private:
     static void *OOM_Malloc(size_t n)
     {
          PMallocHandler mallocHandle;
          void* res;
          __TRACE_DEBUG("一级:第一次申请失败,OOM处理: %d\n", n);
          for (;;)
          {
              mallocHandle = _mallocHandle;
              if (NULL == mallocHandle)
                   throw std::bad_alloc();
              //尝试去释放已经获取但不用的堆空间
              mallocHandle();
              res = malloc(n);
              if (res)//申请成功
                   return res;
          }
     }
     static void *OOM_Realloc(void* p, size_t ,size_t newSize)
     {
          PMallocHandler mallocHandle;
          void* res;
          for (;;)
          {
              mallocHandle = _mallocHandle;
              if (NULL == mallocHandle)
                   throw std::bad_alloc();
              //尝试去释放已经获取但不用的堆空间
              mallocHandle();
              res = realloc(p, newSize);
              if (res)//申请成功
                   return res;
          }
     }
     //释放方法
     PMallocHandler SetMallocHandle(PMallocHandler mallocHandle)
     {
          PMallocHandler old = _mallocHandle;
          _mallocHandle = mallocHandle;
          return old;
     }
private:
     static PMallocHandler _mallocHandle;
};
PMallocHandler MallocAllocTemplate<0>::_mallocHandle = NULL;

二级空间配置器

SGI二级空间配置器的原理是:当区块小于128字节,则以内存池(memory pool)管理,回收时管理一个用户归还的空间,类似于哈希桶。每次配置一块内存,并维护对应的自由链表(free_list)。为了方便管理,SGI二级配置器会对齐到8个字节。(例:需要30字节的空间,自动调整到32字节)。维护16个free_lists,各自管理大小分别为
8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128字节。
这里写图片描述
内存池开始位置:start_free
内存池结束位置:end_free
所需空间的大小:n
内存池大小:heap_size=end_free-start_free.
20个所需的空间大小:total_byte=n*20.

所需空间大小为n,计算索引index=(n+7)/8-1。
查看用户管理的空间中(哈希桶)是否有内存块。有,头删(或者尾删)。如果没有,refill空间。

//计算索引    
 static  size_t FREELIST_INDEX(size_t bytes) {
          return (((bytes)+__ALIGN - 1) / __ALIGN - 1);

这里写图片描述

 //对齐到当前8的整数倍
     static size_t ROUND_UP(size_t bytes) {
          return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
     }

这里写图片描述

refill分几种情况:

1.内存池空间充足(内存池剩余空间>20个所需的空间块),一次性提供。

这里写图片描述

2.内存池剩余空间至少能够提供一个(1个所需的空间块=<内存池剩余空间<20)。将这一个空间返回给用户。

这里写图片描述

扫描二维码关注公众号,回复: 1521753 查看本文章

3.内存池连一个也提供不了(内存池剩余空间<1个所需的空间块),向系统要空间。

step1:计算向系统要的空间大小:2*total_bytes+[heap_size/16]向上8字节对齐

step2:处理剩余空间。将内存池剩下的空间挂在用户管理的空间上。

step3:向系统所索要内存空间。当系统空间充足,malloc成功;当系统空间不足,就会申请失败,会首先在用户管理的内存空间中向后找。如果找到,补充到内存池中,如果找不到,就向一级空间配置器中要空间。

//向桶下边填充内存块
     static void* ReFill(size_t n)
     {
          int nobjs = 20;
          //向内存池中要内存块
          char* chunk = (char*)ChunkAlloc(n, nobjs);

          //内存池中只能够一个内存块
          if (1 == nobjs)
              return chunk;
          size_t index = FREELIST_INDEX(n);
          OBJ* cur = (OBJ*)(chunk + n);//指向下一个内存块
          __TRACE_DEBUG("二级:内存池补充%d个小块内存\n", nobjs);
          //将剩余的内存块挂在链表上
          while (--nobjs)
          {
              //头插法(系统中给的是尾插)
              cur->_freelistlink = _freeList[index];
              _freeList[index] = cur;
              cur = (OBJ*)((char*)cur + n);//向后走一个结点
          }
          return chunk;
     }

整体代码:

#pragma once
#include <iostream>
using namespace std;
#include <string.h>
#include <malloc.h>
#include <stdarg.h>
using namespace std;

#define _DEBUG_
static string GetFileName(const string& path)
{
    char ch = '/';
#ifdef _WIN32
    ch = '\\';
#endif
    size_t pos = path.rfind(ch);
    if (pos == string::npos)
        return path;
    else
        return path.substr(pos + 1);
}

// 用于调试追踪的trace log
inline static void _trace_debug(const char * funcName,
    const char * fileName, int line, char* format, ...)
{
#ifdef _DEBUG_
    fprintf(stdout, "[%s:%d]%s", GetFileName(fileName).c_str(), line, funcName);
    // 输出用户信息
    va_list args;
    va_start(args, format);
    vfprintf(stdout, format, args);
    va_end(args);
#endif
}
#define __TRACE_DEBUG(...) \
    _trace_debug(__FUNCDNAME__, __FILE__, __LINE__, __VA_ARGS__);

//一级空间配置器
//> 128
typedef void (*PMallocHandler)();//是一个类型,函数指针
template <int inst>
class MallocAllocTemplate{
public:
    static void* Allocate(size_t n)
    {
        void *result = malloc(n);
        if (NULL == result)
            result = OOM_Malloc(n);//申请空间失败的应急措施

        __TRACE_DEBUG("一级:%d\n", n);
        return result;
    }

    static void Deallocate(void* p, size_t)
    {
        __TRACE_DEBUG("一级释放\n");
        free(p);
    }

    static void* Reallocate(void* p,size_t,size_t newSize)
    {
        void *result = realloc(p, newSize);
        if (0 == result)
            result = OOM_Realloc(p, newSize);
        return result;
    }

private:
    static void *OOM_Malloc(size_t n)
    {
        PMallocHandler mallocHandle;
        void* res;
        __TRACE_DEBUG("一级:第一次申请失败,OOM处理: %d\n", n);
        for (;;)
        {
            mallocHandle = _mallocHandle;
            if (NULL == mallocHandle)
                throw std::bad_alloc();

            //尝试去释放已经获取但不用的堆空间
            mallocHandle();
            res = malloc(n);
            if (res)//申请成功
                return res;
        }
    }
    static void *OOM_Realloc(void* p, size_t ,size_t newSize)
    {
        PMallocHandler mallocHandle;
        void* res;
        for (;;)
        {
            mallocHandle = _mallocHandle;
            if (NULL == mallocHandle)
                throw std::bad_alloc();

            //尝试去释放已经获取但不用的堆空间
            mallocHandle();
            res = realloc(p, newSize);
            if (res)//申请成功
                return res;
        }
    }

    //释放方法
    PMallocHandler SetMallocHandle(PMallocHandler mallocHandle)
    {
        PMallocHandler old = _mallocHandle;
        _mallocHandle = mallocHandle;
        return old;
    }
private:
    static PMallocHandler _mallocHandle;
};
PMallocHandler MallocAllocTemplate<0>::_mallocHandle = NULL;


//二级空间配置器
//<= 128
template<int inst>
class DefaultAllocateTemplate
{
public:
    static void* Allocate(size_t n)
    {
        if (n > __MAX_BYTES)//大于128字节,使用一级空间配置器分配空间
            return MallocAllocTemplate<0>::Allocate(n);

        __TRACE_DEBUG("二级: %d\n", n);
        size_t index = FREELIST_INDEX(n);

        if (NULL == _freeList[index])//当前的桶下已经没有空间
        {
            __TRACE_DEBUG("二级: %d号桶中(需要空间:%d)没有可以用的空间,需要内存池补充\n",index, n);
            return ReFill(ROUND_UP(n));//给桶下填充空间
        }

        //当前桶下有空间,则会申请空间成功
        void* res = _freeList[index];//第一个内存块的位置
        _freeList[index] = _freeList[index]->_freelistlink;//头删
        return res;//将第一个内存块返给用户
    }

    //向桶下边填充内存块
    static void* ReFill(size_t n)
    {
        int nobjs = 20;
        //向内存池中要内存块
        char* chunk = (char*)ChunkAlloc(n, nobjs);

        //内存池中只能够一个内存块
        if (1 == nobjs)
            return chunk;

        size_t index = FREELIST_INDEX(n);
        OBJ* cur = (OBJ*)(chunk + n);//指向下一个内存块
        __TRACE_DEBUG("二级:内存池补充%d个小块内存\n", nobjs);

        //将剩余的内存块挂在链表上
        while (--nobjs)
        {
            //头插法(系统中给的是尾插)
            cur->_freelistlink = _freeList[index];
            _freeList[index] = cur;
            cur = (OBJ*)((char*)cur + n);//向后走一个结点
        }

        return chunk;
    }

    static void* ChunkAlloc(size_t size, int& nobjs)
    {
        size_t totalBytes = size*nobjs;//需要的空间的大小
        size_t leftBytes = _endFree - _startFree;//剩余的空间大小

        if (leftBytes >= totalBytes)//内存池剩余的空间足够提供(>=20)
        {
            __TRACE_DEBUG("二级:内存池可以提供20个%d个小块内存\n", size);
            char* res = _startFree;
            _startFree += totalBytes;
            return res;//返回给用户使用
        }
        else if (leftBytes >= size)//至少可以提供一个(1~20)
        {
            __TRACE_DEBUG("二级:内存池可以提供%d个%d个小块内存\n", nobjs, size);
            nobjs = leftBytes / size;
            char* res = _startFree;
            _startFree += nobjs*size;
            return res;//将剩余的空间返回给用户使用
        }
        else//一个都不够,向系统要(<1)
        {
            __TRACE_DEBUG("二级:内存池连一个%d字节的小块内存也不能提供\n",size);
            size_t getBytes = totalBytes * 2 + ROUND_UP(_heapSize>>4);//向系统所要空间

            size_t index = FREELIST_INDEX(leftBytes);
            if (leftBytes > 0)//将剩余的空间插入到链表中
            {
                ((OBJ*)_startFree)->_freelistlink = _freeList[index];
                _freeList[index] = (OBJ*)_startFree;
            }

            index = 0;
            _startFree = (char*)malloc(getBytes);
            __TRACE_DEBUG("二级:向内存池中补充空间%d\n", getBytes);

            if (NULL == _startFree)
            {
                //在二级空间配置器中找更大的内存块
                for (index=ROUND_UP(size); index < __MAX_BYTES / __ALIGN; ++index)
                {
                    if (_freeList[index])
                    {
                        __TRACE_DEBUG("二级:向系统堆索要空间失败,找更大的内存块%d字节\n", (index + 1)*__ALIGN);

                        _startFree = (char*)_freeList[index];
                        _freeList[index] = _freeList[index]->_freelistlink;
                        _endFree = _startFree + (index + 1)*__ALIGN;
                        return ChunkAlloc(size, nobjs);
                    }
                }

                _endFree = 0;//防止抛异常
                __TRACE_DEBUG("二级:山穷水尽了,向一级空间配置器索要%d的空间\n", getBytes);

                //向一级空间配置器要空间
                _startFree = (char*)MallocAllocTemplate<0>::Allocate(getBytes);
            }

            //更新
            _endFree = _startFree + getBytes;
            _heapSize += getBytes;
            return ChunkAlloc(size, nobjs);
        }
    }

    //释放
    static void DeAllocate(void* p, size_t n)
    {
        if (n > __MAX_BYTES){
            MallocAllocTemplate<inst>::Deallocate(p, n);
            return;
        }

        //二级空间配置器,释放时将其放入的管理用户空间中
        else
        {
            __TRACE_DEBUG("二级:释放%d\n", n);

            size_t index = FREELIST_INDEX(n);
            ((OBJ*)p)->_freelistlink = _freeList[index];
            _freeList[index] = (OBJ*)p;
        }
    }

private:
    enum { __ALIGN = 8 };
    enum { __MAX_BYTES = 128 };
    enum { __NFREELISTS = __MAX_BYTES / __ALIGN };//哈希桶的大小
private:
    //对齐到当前8的整数倍
    static size_t ROUND_UP(size_t bytes) {
        return (((bytes)+__ALIGN - 1) & ~(__ALIGN - 1));
    }

    union OBJ{
        union OBJ* _freelistlink;//链表中的下一个结点
        char clientdata[1];//当前结点的值
    };

    //计算索引
    static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes)+__ALIGN - 1) / __ALIGN - 1);
    }
private:
    static char* _startFree;
    static char* _endFree;
    static size_t _heapSize;
    static OBJ* _freeList[__NFREELISTS];
};

//静态成员在类外初始化
template<int inst>
char* DefaultAllocateTemplate<inst>::_startFree = NULL;

template<int inst>
char* DefaultAllocateTemplate<inst>::_endFree = NULL;

template<int inst>
size_t DefaultAllocateTemplate<inst>::_heapSize = 0;

template<int inst>
typename DefaultAllocateTemplate<inst>::OBJ* \
DefaultAllocateTemplate<inst>::_freeList[\
DefaultAllocateTemplate<inst>::__NFREELISTS]\
= { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };

#ifdef USE_MALLOC
typedef MallocAllocTemplate<0> _Alloc;
#else
typedef DefaultAllocateTemplate<0> _Alloc;
#endif

template<class T,class Alloc>
class SimpleAlloc
{
public:
    static void* Allocate(size_t n)
    {
        return (0 == n) ? 0 : Alloc::Allocate(sizeof(T)*n);
    }

    static void* Allocate(void)
    {
        return (0 == n) ? 0 : Alloc::Allocate(sizeof(T));
    }

    static void DeAllocate(void* p, size_t n)
    {
        Alloc::DeAllocate(p, n*sizeof(T));
    }

    static void DeAllocate(void* p)
    {
         Alloc::DeAllocate(p, sizeof(T));
    }
};


void testAllocate()
{
    //一级
    int* p1 = (int*)SimpleAlloc<int, _Alloc>::Allocate(100);//100*4>128
    SimpleAlloc<int, _Alloc>::DeAllocate(p1, 100);

    //二级
    int* p2 = (int*)SimpleAlloc<int, _Alloc>::Allocate(2);
    SimpleAlloc<int, _Alloc>::DeAllocate(p2, 2);

    int* p3 = (int*)SimpleAlloc<int, _Alloc>::Allocate(5);

    int* p4 = (int*)SimpleAlloc<int, _Alloc>::Allocate(3);
    SimpleAlloc<int, _Alloc>::DeAllocate(p3, 5);
    SimpleAlloc<int, _Alloc>::DeAllocate(p4, 3);
}

输出结果:

[mallocalloc.h:51]?Allocate@?$MallocAllocTemplate@$0A@@@SAPAXI@Z一级:400
[mallocalloc.h:57]?Deallocate@?$MallocAllocTemplate@$0A@@@SAXPAXI@Z一级释放
[mallocalloc.h:130]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 8
[mallocalloc.h:135]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 0号桶中(需要空间:8)没有可以用的空间,需要内存池补充
[mallocalloc.h:194]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池连一个8字节的小块内存也不能提供
[mallocalloc.h:206]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:向内存池中补充空间320
[mallocalloc.h:179]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池可以提供208个小块内存
[mallocalloc.h:158]?ReFill@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级:内存池补充20个小块内存
[mallocalloc.h:249]?DeAllocate@?$DefaultAllocateTemplate@$0A@@@SAXPAXI@Z二级:释放8
[mallocalloc.h:130]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 20
[mallocalloc.h:135]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 2号桶中(需要空间:20)没有可以用的空间,需 要内存池补充
[mallocalloc.h:186]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池可以提供2024个小块内存
[mallocalloc.h:158]?ReFill@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级:内存池补充6个小块内存
[mallocalloc.h:130]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 12
[mallocalloc.h:135]?Allocate@?$DefaultAllocateTemplate@$0A@@@SAPAXI@Z二级: 1号桶中(需要空间:12)没有可以用的空间,需 要内存池补充
[mallocalloc.h:186]?ChunkAlloc@?$DefaultAllocateTemplate@$0A@@@SAPAXIAAH@Z二级:内存池可以提供2016个小块内存
[mallocalloc.h:249]?DeAllocate@?$DefaultAllocateTemplate@$0A@@@SAXPAXI@Z二级:释放20
[mallocalloc.h:249]?DeAllocate@?$DefaultAllocateTemplate@$0A@@@SAXPAXI@Z二级:释放12

空间配置器也存在一些缺点,但总的来说是优点大于缺点的。
(1)内存并没有还给系统,只换给了空间配置器。有可能在申请大的内存块就不够了。
(2)外部碎片的问题解决了,又引入了内部碎片,自由链表下挂的小结点过多导致。
(3)只有在程序真正退出的时候,内存才释放。

猜你喜欢

转载自blog.csdn.net/zwe7616175/article/details/80559884