[读书笔记]STL源码剖析

内存池STL实现
内存池(Memory Pool)是一种内存分配方式。 通常我们习惯直接使用new、malloc等API申请分配内存,这样做的缺点在于:由于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能
  内存池则是在真正使用内存之前,先申请分配一定数量的、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是尽量避免了内存碎片,使得内存分配效率得到提升。
  在内核中有不少地方内存分配不允许失败. 作为一个在这些情况下确保分配的方式, 内核开发者创建了一个已知为内存池(或者是 "mempool" )的抽象. 一个内存池真实地只是一类后备缓存, 它尽力一直保持一个空闲内存列表给紧急时使用
free-lists 分别管理大小为 8,16,24,32,40,56,64,72,80,88,96,104,112,120,128 bytes 大小的小额区块,所以当申请小额内存时,内存的需求量会被上调至 8 的倍数,以便于管理。
1、如果freelist上有用户所申请的空间大小的位置上有未用空间,则直接分配给用户
2、如果freelist上该大小的未用块没有了,则从内存池中分配20个该大小的快,连接到freelist上,然后分配一个给用户。
3、如果内存池不足以分配20个块,则能分配多少就分配多少;如果一个都不能分配,1)首先把内存池剩下的空间交给对应的freelist 2)然后用malloc申请新的内存池空间。
4、如果malloc申请失败,则查找freelist更大的位置上有没空的,如有则直接分配一个给用户。如没有则呼叫第一级配置器。。。

STL内存管理:如申请的内存大于128字节则直接申请,如小于就*8后使用内存池申请。

using namespace std;
class Block
{
    friend class MemPool;
    size_t size;//块大小,字节为单位
    bool use;
    char *data;//初始为NULL,代表还没分配空间
public:
    Block(){use=false;data=NULL;}
};
 
class MemPool;
class List
{
    friend class Test;
    friend class MemPool;
    size_t size;//本表所存块的大小
    size_t freeBlock;//表示本List还有多少个空的Block
    Block list[20];//每个列表最多有20个block
public:
    List(){freeBlock=0;}
};
 
struct UserMem
{
    size_t size;//此次申请的空间大小
    char* p;//空间的开始指针
};
 
class MemPool
{
    friend class Test;
    List freeList[16];//freelist有15种不同的块大小,从8bytes 到128bytes
    char *freeStart;//当前内存池的开头指针
    size_t poolSize;//内存池的余量
    char* poolStart[10];//新建内存池开始的地址,用于最后删除内存池时delete用,最多10个
public:
    MemPool();
    UserMem myMalloc(size_t size);//客户申请空间函数;默认size为8的倍数
    void myFree(const UserMem &usermem);
    ~MemPool();
};
 
MemPool::~MemPool(){
    //清楚所有内存池空间
    for(int i=0;i<10;++i){
        if(poolStart[i]!=NULL)
        delete []poolStart[i];
    }
}
MemPool::MemPool(){
    freeStart=NULL;
    poolSize=0;
    int tmp=8;
    //初始化size
    for(int i=0;i<16;++i){
        freeList[i].size=tmp;
        tmp+=8;
    }
    //初始化内存池开始地址为NULL,没分配地址的内存池地址为NULL
    for(int i=0;i<10;++i){
        poolStart[i]=NULL;
    }
}
 
UserMem MemPool::myMalloc(size_t size){
    //查找freelist上有没可用的块
    List *thisList=&freeList[size/8-1];//这个大小的块所在的list
    UserMem returnVal;
    //freelist上有可用的
    if(thisList->freeBlock>0){
        for(int i=0;i<20;++i){
            if(thisList->list[i].use || thisList->list[i].data==NULL){continue;}
            else{//找到还没用的Block
                thisList->list[i].use=true;
                thisList->freeBlock--;
                returnVal.size=size;
                returnVal.p=thisList->list[i].data;
                return returnVal;
            }
        }
    }
 
    //freelist上没有可用的
    //从内存池调配空间给freelist
    if(poolSize>=size*5){
        //直接拨5个
        int count=0;
        for(int i=0;i<20;++i){
            if(count<5 && thisList->list[i].data==NULL && poolSize>0){
                thisList->list[i].data=freeStart;
                freeStart+=size;
                poolSize-=size;
                thisList->freeBlock++;
                count++;
            }
        }
        return (myMalloc(size));
    }else if(poolSize>=size){
        //给1个或以上
        int nHasSize=poolSize/size;//可以给nHasSize个
        int count=0;
        for(int i=0;i<20;++i){
            if(count<nHasSize && thisList->list[i].data==NULL && poolSize>0){
                thisList->list[i].data=freeStart;
                freeStart+=size;
                poolSize-=size;
                thisList->freeBlock++;
                count++;
            }
        }
        return (myMalloc(size));
     }else{
        //一个都不能给,把内存池剩余容量给freelist
        cout<<"内存池还剩下"<<poolSize<<"个字节的内存"<<endl;
        if(poolSize>0){
 
            List *returnToList=&freeList[poolSize/8-1];
            for(int i=0;i<20;++i){
                if(returnToList->list[i].data==NULL){
                    returnToList->list[i].data=freeStart;
                    returnToList->freeBlock++;
                    poolSize-=returnToList->size;
                    break;
                }
            }
        }
        cout<<"内存池还剩下"<<poolSize<<"个字节的内存"<<endl;
        //给内存池new10个size给内存池,然后调用自己。
        freeStart=new char[size*10];
        if(freeStart==NULL){
            cout<<"new failed"<<endl;
            return (UserMem){0,NULL};
        }
        for(int i=0;i<10;++i){
            if(poolStart[i]==NULL){
                poolStart[i]=freeStart;
                break;
            }
        }
        poolSize+=size*10;
        return (myMalloc(size));
     }
}
 
void MemPool::myFree(const UserMem &usermem){
    List *thisList=&freeList[usermem.size/8-1];
    int index=((int)usermem.p-(int)thisList->list[0].data)/usermem.size;
    thisList->list[index].use=false;
    thisList->freeBlock++;
}
 
class Test
{
public:
    static void test(const MemPool &memPool);
    static UserMem useMem(MemPool &memPool,size_t size);
    static void freeMem(MemPool &memPool,const UserMem &userMem);
};
void Test::test(const MemPool &memPool){//内存池的测试函数
    //打印内存池的大小
    cout<<"内存池大小为:"<<memPool.poolSize<<endl;
    //打印freelist的使用情况
    for(int i=0;i<16;++i){
        const List *thisSizeList=&memPool.freeList[i];
        //打印此大小的list的信息
        cout<<"大小为"<<thisSizeList->size<<"的表还剩"<<thisSizeList->freeBlock<<"个块可用"<<endl;
    }
}
 
UserMem Test::useMem(MemPool &memPool,size_t size){
    UserMem rVal=memPool.myMalloc(size);
    char *p=rVal.p;
    if(p!=NULL){
        memset(p,'a',size-1);
        p[size-1]='\0';
        cout<<p<<endl;
    }else{
        cout<<"error"<<endl;
    }
    return rVal;
}
 
void Test::freeMem(MemPool &memPool,const UserMem &userMem){
    memPool.myFree(userMem);
}
 
int main()
{
    MemPool mem;
    Test::test(mem);
    Test::freeMem(mem,Test::useMem(mem,8));
    Test::useMem(mem,128);
    Test::test(mem);
    return 0;
}

list:

就算列表为空,也回存在一个空空节点,end()就是指向这个空节点。这个空节点往后连接到最后一个元素,往前连接到begin(),因此用一个成员endNode来记录这个空节点的迭代器,就可以方便的实现很多操作。如begin()为endNode->next();end()为endNode等等。并且此设计符合STL的前闭后开的规定,比如一个函数要求传入一个迭代器区间,则(beging(),end()),表示所有元素。
 
deque:
双向队列也是一个随机存取容器。但与vector不同,它的元素并不存储在连续的内存中,只是存储在分段连续的内存中,就是连接多段连续内存。每次连续空间不足时,并不需要搬运数据,只需在寻找一个新的连续内存,并把它连接到之前的内存上就可以了。这个可以避免搬运内存而带来的低效,但代价是复杂的迭代器设计。

deque使用一块连续地址的map存放各段连续的缓冲空间的头指针。缓冲空间才是deque真正的数据存储区。

posted @ 2012-12-12 15:42  iyjhabc  阅读(262)  评论(0编辑  收藏  举报