• STL的空间配置器std_alloc 笔记


    STL的空间配置器std_alloc 笔记

    C++的内存分配基本操作是 ::operator new(),内存释放是 ::operator delete(),这里两个全局函数相当于C的malloc和free; 
    std::alloc 设计了双层配置器,第一层直接用了malloc和free,内存不足时用函数指针模拟C++中set_new_handler方式进行自定义处理函数;第二层:超过128bytes时,便使用第一层配置器,小于128bytes则采用memory pool方式。

    1,第一级配置器 __malloc_alloc_template

    用C模拟C++中set_new_handler方式处理oom的原因C++并未提供realloc()的内存重分配操作加其他历史原因。

    static void *oom_malloc(size_t);
    static void *oom_realloc(void *, size_t);
    static void (* __malloc_alloc_oom_handler)();
    
    static void (* set_malloc_handler(void (*f)())) () {
        void (* old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return (old);
    }
    
    // 初始值设为0,留给客户端自行配置(调用set_malloc_handler配置)
    void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler) () = 0;

    第一级的allocate和deallocate其实就是malloc和free的简单封装和oom处理,oom处理即通过上面的设置的处理函数 来 反复调用 以及 malloc或realloc来分配内存知道成功;如果不设置__malloc_alloc_oom_handler,直接__THROW_BAD_ALLOC抛出bad_alloc,直接终止程序。

    2,第二级配置器__default_alloc_template

    小于128bytes通过第二级配置器分配内存,避免太多小额区块造成内存碎片。 
    自由链表数据结构,free_lists管理的大小分别为8,16,24,…,128bytes,共16个链表结点。 
    自由链表结点如下,

    union obj {
        union obj *free_list_link;
        char client_data[1];
    };

    union节省了额外内存负担,未使用时视为obj指针,指向相同形式的另一个obj;实际使用时,视为char 指针,指向实际区块。 
    __default_alloc_templateROUND_UP比较有技巧性,避免除法的使用,改用位运算上调至8的倍数。

    enum { __ALIGN = 8 };  //小区块的上调边界
    
    // 将bytes上调至8的倍数
    // testcase: 比如传入 bytes = 7;(0b1110 & ~7),  ~7 = -8, 负数的二进制特殊0b...11111000(高位均为1), &操作的结果=8,完美避免除法的计算。 
    static size_t ROUND_UP (size_t bytes) {
        return (((bytes) + __ALIGN - 1) & ~(__ALIGN - 1));
    }

    2.1 allocate函数

    • 1,大于128bytes,直接跳转至第一级分配器;
    • 2,如上图,根据传入的大小找到free_lists中合适的区块;
    • 3,返回值设置为第一个可用指针;
    • 4,my_free_list指向下一个可用区块;

    2.2 deallocate(void *p, size_t n)函数

    • 1,大于128bytes,直接跳转至第一级分配器;调用释放函数;
    • 2,临时指针q指向传入的需要释放的p;
    • 3,如上图,根据传入的大小找到free_lists中合适的区块;
    • 4,q->free_list_link指向my_free_list的可用区块;
    • 5,my_free_list再指回q,即q再为可用区块,相当于回收(并未真正释放,只是再次纳入可用,下次直接可用于其他的数据分配)。

    2.3 重新填充refill函数

    前面的allocate函数失败时,会调用refill重新填充free_lists中传入大小的槽位的空间,通过调用chunk_alloc。如果只取到一个区块,直接返回;如果多个需要一个个串接起来(obj->free_list_link循环指针操作)。详细说明下面的代码注释。

    ...
    void* refill(size_t n) {
        ...
        //取到多个区块后循环指针操作
        my_free_list = free_list + FREELIST_INDEX(n);
    
        result = (obj *)chunk;  //这一块准备返回给客户端,即已经将要被使用,不再被纳入free范畴
        *my_free_list = next_obj = (obj *)(chunk + n);  //free范畴的区块,从下一块开始
        //第一个i = 0已经是将要被客户端使用的区块,从1开始
        for (int i = 1; ; i++) {
            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;
    }

    2.4 内存池取空间给free_list,chunk_alloc(size_t size, int &nobjs) 函数

    内存池可用空间通过size_t bytes_left = end_free - start_free; 得来;

    • 1,第一种情况内存池剩余空间满足需求,直接返回start_free,再更新start_free += total_bytes; 的地址,nobjs不变,等于默认值20;
    • 2,第二种情况内存池剩余空间不足以满足全部需求,但能满足一个以上的需求;做除法,能满足几个是几个,其他同情况一,唯一修改nobjs的地方,不足20时,实际分配的数量;
    • 3,第三种情况内存池连一个区块的大小都无法提供; 
      • 首先从内存池中找残余的空间,调整free list,将内存池中残余空间编入;
      • 分配两倍于需求量 + 随配置次数增加的附加量 
        //首先设置将要分配的更多的内存以补充内存池的区块大小 
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); 
      • 如果分配失败,循环检测内存池各大小区块可用的空间,如图中的 #7 (64bytes) 从 32bytes末端取内存池中残余空间;递归调用chunk_alloc,最终会在1或2中终止,nobjs <= 20; 
        如果更严重的情况,到处都没有内存可用,调用第一级配置器,其中有oom处理机制;
      • 如果成功,如图96bytes对应20个区块,剩余的加入内存池,供后续使用。递归调用chunk_alloc,最终会在1或2中终止,nobjs <= 20。

    小结

    std::alloc 设计了双层配置器,超过128bytes时,便使用第一层配置器,简单封装malloc和free,oom处理函数等;小于128bytes通过第二级配置器分配内存,内存池方式处理小块内存,避免太多小额区块造成内存碎片。

    参考

    《STL 源码剖析》

  • 相关阅读:
    [kuangbin带你飞]专题十六 KMP & 扩展KMP & ManacherK
    [kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher J
    [kuangbin带你飞]专题十六 KMP & 扩展KMP & Manacher I
    pat 1065 A+B and C (64bit)(20 分)(大数, Java)
    pat 1069 The Black Hole of Numbers(20 分)
    pat 1077 Kuchiguse(20 分) (字典树)
    pat 1084 Broken Keyboard(20 分)
    pat 1092 To Buy or Not to Buy(20 分)
    pat 1046 Shortest Distance(20 分) (线段树)
    pat 1042 Shuffling Machine(20 分)
  • 原文地址:https://www.cnblogs.com/edisongz/p/7039421.html
Copyright © 2020-2023  润新知