SGI STL 之空间配置器

来源:互联网 发布:宋定伯卖鬼 知乎 编辑:程序博客网 时间:2024/05/22 06:41

一 .SGI STL之空间配置器

  身为C++标准库最重要的组成部分,STL(标准模板库)不仅是一个可复用组件库,而且是一个包罗万象算法与数据结构的软件框架.STL实现版本多样,而SGI版本不论在技术层次,源代码组织,源代码可读性上,均有卓越的表现.
  以STL的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件的背后,默默工作.但从STL的实现角度而言,空间配置器又是极其重要的,因为整个STL的操作对象都放在容器之内,而容器一定需要配置空间以置放资料.
    并且阅读和剖析名家代码是提高编程水平的捷径.源码之前,了无秘密.大师们的缜密思维,经验结晶,技术思路,独到风格,都原原本本体现在源码之中.在你仔细推敲之中,迷惑不解之时,恍然大悟之际,你的经验,思维,视野,知识乃至技术品味都会获得快速的成长…


二.双层级配置器简介

  SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则是情况采用不同的策略:当配置区块超过128bytes时,视之为"过小",为了降低额外负担,便采用复杂的内存池(memory pool)整理方式,而不再求助于第一级配置器. 一二级配置器的关系,及实际运用方式,见下图:
这里写图片描述


1.第一级配置器

  第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置,释放,重新配置操作,并实现出类似C++ new_handler的机制.它不能直接运用C++ new_handler机制,因为它并非使用::operator new 来配置内存.所谓C++ new handler机制是,你可以要求系统在内配置需求无法被满足时,调用一个你所指定的函数.换句话说,一旦::operator new 无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程.该例程通常即被称为new_handler. new_handler 解决内存不足的做法有特定的模式,可参考<<Effective C++>>2e 条款7.
  SGI第一级配置器的allocate()和realloc()都是在调用malloc和realloc()不成功后,该调用oom_malloc()和oom_realloc().后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务.但如果"内存不足处理例程”并未被客端设定,oom_malloc()和oom_realloc()便调用__THROW_BAD_ALLOC, 丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序.


2.第二级配置器

  第二级配置器多了一些机制,避免太多小额区块造成内存的碎片.小额区块带来的其实不仅是内存碎片,配置时的额外负担也是一个大问题.额外负担永远无法避免,毕竟系统要靠这多出来的空间来管理内存.若区块越小,额外负担所占的比例就越大,越显得浪费.
  SGI第二级配置器的做法是,如果区块够大,超过128bytes时,就移交第一级配置器处理.当区块小于128bytes时,则以内存池(memory pool)管理.此法又称为次层配置:每次配置一大块内存,并维护对应之自由链表(free_list).下次若再有相同大小的内存需求,就直接从free_lists中拨出.如果客端释还小额区块,就有配置器回收到free_lists中(配置器除了负责配置,也负责回收).为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客端要求30bytes,就自动调整为32bytes), 并维护16个free_lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块.

refill()函数

  当调用allocate()函数申请空间时,它会从free_list中寻找内存,当free_list中没有可用区块时,就调用refill(),准备为free_list重新填充空间,新的空间将取自内存池(经由chunk_alloc()完成).refill函数会接收一个额外的返回值(传引用的nobjs), 函数根据这个值做出不同的响应.
  

chunk_alloc()函数

  从内存池中取空间给free_list使用是chunk_alloc()函数的工作: chunk_alloc()函数以end_free - start_free来判断内存池的水量.如果水量充足,就直接调出20个区块返回给free_list.如果水量不足以提供20个区块,但还足够供应一个以上的区块,就拨出这不足20个区块的空间出去.这时候器传引用的njobs参数将被修改为实际能供应的区块数.如果内存池连一个区块空间都无法供应,对客端显然无法交待,此时便需要利用malloc()从heap中配置内存,为内存池注入活水源头以应付需求.新水量的大小为需求量的两倍,再加上一个随着配置次数增加而越来越大的附加量.
  举个例子,假设程序一开始,客端就调用chunk_alloc(32,20),于是malloc()配置40个32bytes区块,其中第一个交出,另19个交给free_list[3]维护,余20个留给内存池.接下来客端调用chunk_alloc(64, 20),此时free_list[7]空空如也,必须想内存池要求支持.内存池只够供应(32*20)/64=10个64bytes区块,就把这10个区块返回,第一个交给客端,余下9个由free_list[7]维护.此时内存池全空.接下来在调用chunk_alloc(96,20),此时free_list[11]空空如也,必须向内存池要求支持,而内存池此时也是空的,于是以malloc()配置40+n(附加量)个96bytes区块,其中第1个交出,另19个交给free_list[11]维护,余20+n(附加量)个区块留给内存池…
  万一山穷水尽,整个system heap空间都不够了(以至于无法为内存池诸如活水源头),malloc()行动失败,chunk_alloc()就四处寻找有无"尚有未用区块,且区块够大"之free_lists.找到了就挖一块交出,找不到调用第一级配置器.第一级配置器其实也是使用malloc()来配置内存,但它有out_of_memory处理机制(类似new_handler机制),或许有机会释放其他的内存拿来此处使用.如果可以,就成功,否则就发出异常.
以上便是整个第二级空间配置器的设计.


三.代码及测试

stl_alloc.h

#if 1#include<iostream>#include<new>#include<malloc.h>using namespace std;//#define __THROW_BAD_ALLOC   throw   bad_alloc#define __THROW_BAD_ALLOC  cerr<<"Throw bad alloc, Out Of Memory."<<endl; exit(1)#elif  !defined  (__THROW_BAD_ALLOC)#include<iostream.h>#define __THROW_BAD_ALLOC   cerr<<"out of memory"<<endl; exit(1);#endif//一级配置器template<int inst>class __malloc_alloc_template{//oom : out of memoryprivate:    static void* oom_malloc(size_t);    static void* oom_realloc(void *, size_t);    static void(* __malloc_alloc_oom_handler)(); //函数指针,代表的函数将用来处理内存不足的情况public:    static void* allocate(size_t n)    {        void *result = malloc(n); //第一级配置器直接使用malloc()    //malloc()无法满足需求时,改用 oom_malloc()        if(0 == result)            result = oom_malloc(n);        return result;    }    static void deallocate(void *p, size_t)    {        free(p); //第一级配置器直接使用free()    }    static void* reallocate(void *p, size_t, size_t new_sz)    {        void *result = realloc(p, new_sz); //第一级配置器直接使用realloc()    //realloc()无法满足需求时,改用oom_realloc()        if(0 == result)            oom_realloc(p,new_sz);        return result;    }public:    //set_new_handler(Out_Of_Memory);    //以下仿真C++的set_new_handler(),可以通过它指定你自己的out_of_memory handler    static void(*set_malloc_handler(void(*f)()))()    {        void(*old)() = __malloc_alloc_oom_handler;        __malloc_alloc_oom_handler = f;        return (old);    }};//初值为0,有待客端设定template<int inst>void (*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;template<int inst>void* __malloc_alloc_template<inst>::oom_malloc(size_t n){    void *result;    void(* my_malloc_handler)();    for(;;) //不断尝试释放,配置,再释放,再配置...    {        my_malloc_handler = __malloc_alloc_oom_handler;        if(0 == my_malloc_handler)        {             __THROW_BAD_ALLOC;        }        (*my_malloc_handler)(); //调用处理程序,企图释放内存        result = malloc(n); //再次尝试配置内存        if(result)            return result;    }}template<int inst>void* __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n){    void(*my_malloc_handler)();    void *result;    for(;;) //不断尝试释放,配置,再释放,再配置...    {        my_malloc_handler = __malloc_alloc_oom_handler;        if(0 == my_malloc_handler)        {            __THROW_BAD_ALLOC;        }        (*my_malloc_handler)(); //调用处理程序,企图释放内存        result = realloc(p, n); //再次尝试配置内存        if(result)            return result;    }}typedef __malloc_alloc_template<0> malloc_alloc;///////////////////////////////////////////////////////////////////////////////////////二级配置器enum {__ALIGN = 8}; //小型区块的上调边界enum {__MAX_BYTES  = 128}; //小型区块的上限enum {__NFREELISTS = __MAX_BYTES / __ALIGN}; //free_lists个数template<bool threads, int inst>class __default_alloc_template{public:    static void* allocate(size_t n);    static void  deallocate(void *p, size_t n);    static void* reallocate(void *p, size_t, size_t new_sz);private:    static size_t  ROUND_UP(size_t bytes)    {//将bytes上调至8的倍数        return (((bytes) + __ALIGN-1) & ~(__ALIGN-1));    }private:    //free_lists的节点结构    union obj    {        union obj * free_list_link;        char client_data[1];    };private:    static obj* volatile free_list[__NFREELISTS]; //16个free_lists    static size_t FREELIST_INDEX(size_t bytes)    {//根据区块大小,决定使用第n号的free_lists        return ((bytes)+__ALIGN-1) / __ALIGN - 1;    }private:    static char *start_free; //内存池起始位置,只在chunk_alloc()中变化    static char *end_free;   //内存池结束位置,只在chunk_alloc()中变化    static size_t heap_size;    static void *refill(size_t n); //返回一个大小为n的对象,并可能加入大小为n的其它区块到free_list    static char* chunk_alloc(size_t size, int &nobjs); //配置一大块空间,可容纳njobs个大小为"size"的区块};//以下是static data member的定义与初始值设定template<bool threads, int inst>char* __default_alloc_template<threads, inst>::start_free = 0;template<bool threads, int inst>char* __default_alloc_template<threads, inst>::end_free = 0;template<bool threads, int inst>size_t __default_alloc_template<threads, inst>::heap_size = 0;template<bool threads, int inst>typename __default_alloc_template<threads, inst>::obj* volatile__default_alloc_template<threads, inst>::free_list[__NFREELISTS] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};template<bool threads, int inst>void* __default_alloc_template<threads, inst>::allocate(size_t n){    obj * volatile *my_free_list;    obj *result;    //大于128就调用第一级配置器    if(n > __MAX_BYTES)    {        return malloc_alloc::allocate(n);    }    my_free_list = free_list + FREELIST_INDEX(n); //寻找16个free_lists中合适的一个    result = *my_free_list;    if(result == 0)    {//没有找到可用的free_list,准备重新填充free_list        void *r = refill(ROUND_UP(n));        return r;    }    *my_free_list = result->free_list_link; //调整free_list    return result; }template<bool threads, int inst>void* __default_alloc_template<threads, inst>::refill(size_t n){    int nobjs = 20; //申请20*2个块,一半挂在自由链表,一半用作内存池(20为一个经验值)    char *chunk = chunk_alloc(n, nobjs); //调用chunk_alloc(),尝试取得nobjs个区块作为free_list的新节点(njobs为传引用)    obj * volatile *my_free_list; //my_free_list为二级指针,volatile为一个类型修饰符,与线程相关    obj *result;    obj *current_obj, *next_obj;    int i;    if(1 == nobjs) //如果只获得一个区块,这个区块就分配给调用者,free_list无新节点        return chunk;    //否则准备调整free_list,并纳入新节点    my_free_list = free_list + FREELIST_INDEX(n);    result = (obj*)chunk; //这一块准备返回给客端    //以下引导free_list指向新配置的空间(取自内存池)    *my_free_list = next_obj = (obj*)(chunk+n);    //以下将free_list的各节点串接起来    for(i=1; ; ++i) //从1开始,第0个将返回给客端    {        current_obj = next_obj;        next_obj = (obj*)((char*)next_obj+n);        if(nobjs - 1 == i)        {            current_obj->free_list_link = 0;            break;        }        else        {            current_obj->free_list_link = next_obj;        }    }    return (result);}//注意nobjs传引用,假设size已经上调至8的倍数template<bool threads, int inst>char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int &nobjs){    char *result;    size_t total_bytes = size * nobjs;    size_t bytes_left = end_free - start_free; //内存池剩余空间    if(bytes_left >= total_bytes)    {//内存池剩余空间完全满足需求量        result = start_free;        start_free += total_bytes;        return result;    }    else if(bytes_left >= size)    {//内存池剩余空间只足够供应一个(含)以上的区块,但不能完全满足需求量        nobjs = bytes_left / size;        total_bytes = size * nobjs;        result = start_free;        start_free += total_bytes;        return result;    }    else    {//内存池剩余空间连一个区块的大小都无法提供        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);    //以下尝试这让内存池中的残余零头还有利用价值        if(bytes_left > 0)        {        //内存池内还有一些零头,先配给适当的free_list            obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);        //调整free_list,将内存池的剩余空间编入            ((obj*)start_free)->free_list_link = *my_free_list;            *my_free_list = (obj *)start_free;        }    //配置heap空间,用来补充内存池        start_free = (char *)malloc(bytes_to_get);        if(0 == start_free)        {            int i;            obj * volatile *my_free_list, *p;        //试着检视我们手上拥有的东西,这不会造成伤害,我们不打算尝试配置较小的区块,因为那在多进程机器上容易造成灾难,以下搜寻适当的free_list,所谓适当是指"尚有未用区块,且区块够大"之free_list            for(i=size; i<=__MAX_BYTES; i += __ALIGN)            {                my_free_list = free_list + FREELIST_INDEX(i);                p = *my_free_list;                if(0 != p)                {//free_list内尚有未用区块,调整free_list以释放未用区块                    *my_free_list = p->free_list_link;                    start_free = (char *)p;                    end_free = start_free + i;                    return chunk_alloc(size, nobjs); //递归调用自己,为了修正nobjs                }            }        //如果出现意外(山穷水尽,到处都没内存可用了),调用第一级配置器,看看out_of_memory机制能否尽点力            end_free = 0;            start_free = (char *)malloc_alloc::allocate(bytes_to_get); //这会导致抛出异常,或内存不足的情况得到改善        }        heap_size  += bytes_to_get;        end_free = start_free + bytes_to_get;    return chunk_alloc(size, nobjs); //递归调用自己,为了修正nobjs    }}template<bool threads, int inst>void __default_alloc_template<threads, inst>::deallocate(void *p, size_t n){    obj *q = (obj *)p;    obj * volatile * my_free_list;    if (n > (size_t) __MAX_BYTES)    {//大于128就调用第一级配置器        malloc_alloc::deallocate(p, n);        return;    }    my_free_list = free_list + FREELIST_INDEX(n); //寻找对应的free_list    //调整free_list,回收区块    q->free_list_link = *my_free_list;    *my_free_list = q;}

memory

#include"stl_alloc.h"

test.cpp

#include<iostream>#include<stdlib.h>#include "memory"using namespace std;void Out_Of_Memory(){    cout<<"Out Of Memory."<<endl;    exit(1);}int main(){        void (*pfun)() = __malloc_alloc_template<0>::set_malloc_handler(Out_Of_Memory);//语句1        int *p = (int*)__malloc_alloc_template<0>::allocate(sizeof(int) * 2073741824);        if(p == NULL)        {                cout<<"Error."<<endl;                exit(1);        }        cout<<"OK"<<endl;        return 0;}

test2.cpp

#include<iostream>#include<stdlib.h>#include "memory"using namespace std;int main(){    char *p1 = (char *)__default_alloc_template<0,0>::allocate(sizeof(char) * 32);    char *p2 = (char *)__default_alloc_template<0,0>::allocate(sizeof(char) * 32);    char *p3 = (char *)__default_alloc_template<0,0>::allocate(sizeof(char) * 64);    char *p4 = (char *)__default_alloc_template<0,0>::allocate(sizeof(char) * 96);    return 0;}

test.cpp 运行结果如下:
不加语句1, 调用默认的处理程序
这里写图片描述
加上语句1,调用了Out_Of_Memory()处理程序
这里写图片描述

test2.cpp:
这个测试没有输出结果,但每句可能会走不同的代码分支,可以根据之前介绍chunk_allco()函数时的例子,用单步调试的方法进行跟踪,就能够了解空间配置器是如何配置空间了.

1 0
原创粉丝点击