首页 > 编程语言 >【C++】空间配置器

【C++】空间配置器

时间:2024-09-02 16:23:30浏览次数:13  
标签:__ obj void 配置 list free C++ 空间


空间配置器,听起来高大上,那它到底是什么东西呢?

1.什么是空间配置器?

空间配置器是STL源码中实现的一个小灶,用来应对STL容器频繁申请小块内存空间的问题。他算是一个小型的内存池,以提升STL容器在空间申请方面的效率

【C++】空间配置器_空间配置器

2.了解空间配置器

STL以128个字节为分界线,将空间配置器分为了一级和二级

2.1 一级

一级空间配置器中,allocate/deallocate函数实际上就是对malloc/free做了一个简单的封装

static void * allocate(size_t n)
{
    void *result = malloc(n);
    if (0 == result) result = oom_malloc(n);
    return result;
}

static void deallocate(void *p, size_t /* n */)
{
    free(p);
}

//这个函数是malloc失败的时候才会调用的
template <int inst>
void * __malloc_alloc_template<inst>::oom_malloc(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 = malloc(n);
        if (result) return(result);
    }
}

当malloc失败的时候,一级空间配置器会抛异常

2.2 二级

在二级空间配置器中,会先判断带申请空间大小是否大于128个字节,如果超过了,则会去调用一级空间配置器

# ifndef __SUNPRO_CC
enum {__ALIGN = 8};
enum {__MAX_BYTES = 128};
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};
# endif  
/* n must be > 0      */
static void * allocate(size_t n)
{
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;

    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    // Acquire the lock here with a constructor call.
    // This ensures that it is released in exit or during stack
    // unwinding.
    #       ifndef _NOTHREADS
    /*REFERENCED*/
    lock lock_instance;
    #       endif
    result = *my_free_list;
    if (result == 0) {
        void *r = refill(ROUND_UP(n));
        return r;
    }
    *my_free_list = result -> free_list_link;
    return (result);
};

/* p may not be 0 */
static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    if (n > (size_t) __MAX_BYTES) {
        malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n);
    // acquire lock
    #       ifndef _NOTHREADS
    /*REFERENCED*/
    lock lock_instance;
    #       endif /* _NOTHREADS */
    q -> free_list_link = *my_free_list;
    *my_free_list = q;
    // lock is released here
}

二级空间配置器中,主要的几个成员变量如下

static obj * __VOLATILE free_list[__NFREELISTS]; //哈希桶
// Chunk allocation state.
static char *start_free;
static char *end_free;
static size_t heap_size;

这里是一个内存池+ 一个哈希桶,内存池的长度为heap_size

【C++】空间配置器_空间配置器_02

哈希桶以8字节对齐,分为了16个桶。最开始的时候,free_list哈希桶是空的

【C++】空间配置器_空间配置器_03

当我们需要小于128个字节空间的时候(以16字节为例),二级空间配置器会直接去上面的内存池当中申请20个16字节的空间,链接到16字节对应的哈希桶的位置(1号桶)

在下面的refill函数中可以看到这一点

template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;//一次性申请20个
    char * chunk = chunk_alloc(n, nobjs);
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    if (1 == nobjs) return(chunk);
    my_free_list = free_list + FREELIST_INDEX(n);

    /* Build free list in chunk */
      result = (obj *)chunk;
      *my_free_list = next_obj = (obj *)(chunk + n);
      for (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);
}

8字节对齐的妙处

假设我们的list单个节点需要12个字节,set单个字节需要16个字节

当list需要空间的时候,二级空间配置器也会去申请16个字节的空间,而不会去申请12个字节。这时候,当list将用完的空间还回来之后,就能拿过去给set用了!

关于哈希桶的特殊处理

当我们用完了申请的空间,准备“释放”的时候,二级空间配置器会将释放回来的空间插入进哈希桶(头插)

这里有一个特殊处理,当用完的空间,或者说是刚申请的空间插入进哈希桶的时候,二及空间配置器会将其强转为一个obj类型,这个类型的前4/8个字节会用来存放一个指针,指向下一个空间,以此构成链表

union obj {
    union obj * free_list_link;
    char client_data[1];    /* The client sees this.        */
};

【C++】空间配置器_封装_04

当我们再次需要这部分大小的空间的时候,只需要将哈希桶头指针的指向直接修改,就能链接上下一个空间,并将之前头指针指向的空间拿去用

用的时候也不用管之前在此处存放的指针,毕竟下一次放回来的时候,二级空间配置器会来处理它的


内存池空间不够用了咋办?

之前提到了,二级空间配置器里面有一个小内存池。要是这个内存池用完了,要咋整呢?

二级空间配置器想出来了一种很骚的做法:

  • 假设当前我们需要24字节的空间
  • 对应的桶下面没有空间给我们用
  • 内存池也用光了
  • 但是48字节的桶下面还有空间可以用
  • 直接把这个空间拿过来,拆成两个24链接到24的桶下面!

这样便有效提高了空间利用率,也避免了realloc造成的时间消耗。

当然,要是桶里面也没有多余空间了,那就只能老老实实去扩容了

只能大的拆小的,小的空间不连续无法组成大的

//二级空间配置器里面的reallocate封装
template <bool threads, int inst>
void*
__default_alloc_template<threads, inst>::reallocate(void *p,
                                                    size_t old_sz,
                                                    size_t new_sz)
{
    void * result;
    size_t copy_sz;

    if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {
        return(realloc(p, new_sz));
    }
    if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return(p);
    result = allocate(new_sz);
    copy_sz = new_sz > old_sz? old_sz : new_sz;
    memcpy(result, p, copy_sz);
    deallocate(p, old_sz);
    return(result);
}

2.3 关于单例的说明

在同一个进程里面,空间配置器只会有一个

但STL库中的空间配置器并没有把自己设计成单例类,而是用了一个间接的做法

static obj * __VOLATILE free_list[__NFREELISTS]; //哈希桶
// Chunk allocation state.
static char *start_free;
static char *end_free;
static size_t heap_size;

那就是所有成员变量都是static静态的

我们知道,在类和对象中,静态成员变量是属于整个类的,所以它也是一种单例模式!


2.4 空间配置器的优点

  • 空间配置器的时间复杂度是O(1),申请空间的效率很高
  • 空间配置器能解决频繁申请空间导致的内存碎片问题

当我们使用STL容器频繁申请小块空间(比如list)就容易导致堆区中有非常多的小块空间被使用,而无法申请一个大的空间

空间配置器提前申请了一个大块空间再私下管理,可以有效解决这个问题

内存外碎片/内碎片

  • 外碎片:多次申请小块空间造成。有足够内存,但是不连续,无法申请大块内存
  • 内碎片:list只需要12个字节,而空间配置器因为内存对齐而给了它16个字节,浪费了4个字节造成的内存碎片

3.容器和空间配置器

之前学习stl容器的时候,就在定义中看到过这里的alloc默认模板参数

【C++】空间配置器_空间配置器_05

这里默认传的便是stl库中的二级空间配置器

只要你自己写的空间配置器符合stl的接口需求,你便可以将自己的空间配置器传入此处让容器使用!

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef __default_alloc_template<false, 0> single_client_alloc;

在list中,alloc又被封装了一层,用来处理节点申请的问题

template <class T, class Alloc = alloc>
class list {
protected:
  typedef void* void_pointer;
  typedef __list_node<T> list_node;
  typedef simple_alloc<list_node, Alloc> list_node_allocator;
template<class T, class Alloc>
class simple_alloc {

public:
    static T *allocate(size_t n)
                { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
    static T *allocate(void)
                { return (T*) Alloc::allocate(sizeof (T)); }
    static void deallocate(T *p, size_t n)
                { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
    static void deallocate(T *p)
                { Alloc::deallocate(p, sizeof (T)); }
};

随后,当创建节点、销毁节点的时候,list就会调用封装好的simple_alloc来处理空间

link_type get_node() { return list_node_allocator::allocate(); }
void put_node(link_type p) { list_node_allocator::deallocate(p); } 
link_type create_node(const T& x) {
    link_type p = get_node();
    __STL_TRY {
      construct(&p->data, x);//调用节点的构造函数
    }
    __STL_UNWIND(put_node(p));
    return p;
  }
  void destroy_node(link_type p) {
    destroy(&p->data);//调用节点的析构函数
    put_node(p);
  }
  • construct通过定位new显式调用节点的构造函数
  • destroy通过指针来调用析构函数
template <class T>
inline void destroy(T* pointer) {
    pointer->~T();
}

template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
  new (p) T1(value);
}

stl_tree.h中可以看到库里面的红黑树也对空间配置器进行了类似的封装

template <class Key, class Value, class KeyOfValue, class Compare,
          class Alloc = alloc>
class rb_tree {
protected:
  typedef void* void_pointer;
  typedef __rb_tree_node_base* base_ptr;
  typedef __rb_tree_node<Value> rb_tree_node;
  typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;
  typedef __rb_tree_color_type color_type;

结语

关于空间配置器的内容了解这些就差不多啦!

其实就是通过库里面的这个设计来了解一个提高小空间内存申请效率的方法,感觉还是很666的

有啥问题可以在评论提出哦


标签:__,obj,void,配置,list,free,C++,空间
From: https://blog.51cto.com/musnow/11899009

相关文章

  • C++中namespace的用法
    我们在现实的项目开发中一般会有着大量的代码,而且代码都是多人编写的,也许一个项目会有10个功能,每一个人都要完成一个功能。但是敲过代码的都知道,一般在编写程序的时候如果多人没有实现约定去完成,那就会出现代码冲突的情况,那么,为了解决这样的冲突,我们C++中使用了命名空间namesp......
  • NuGet配置文件nuget.config的应用示例
    参考:https://learn.microsoft.com/zh-cn/nuget/consume-packages/configuring-nuget-behavior创建新配置文件使用.NETCLI运行  dotnetnewnugetconfig ,创建默认nuget.config。有关详细信息,请参阅dotnetCLI命令。也可以将下方的模板手动复制到新文件中,然后使用......
  • nginx的基本使用示例(负载均衡,虚拟主机,动静分离)的详细配置过程
    文章目录前言前置工作httpd主机tomcat主机nginx主机配置负载均衡配置过程效果展示虚拟主机配置过程效果展示动静分离配置过程排除思路前言本篇博客展示nginx的基本使用案例,后端由httpd+tomcat组成,linux版本:rocky9.2虚拟机ipnginx192.168.10.11httpd192.168......
  • Windows server 2012 R2配置NTP Server
    将服务器类型更改成NTP:注册表中找到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Parameters下Type值改成"NTP";将注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\Config下AnnounceFlag项值改成"5";启用NTPServer:将注册表中HKEY_LOCAL......
  • 多重背包问题 模板 C++实现
    问题:有n 种物品和一个容量是c 的背包。第i种物品最多有num[i-1] 件,每件体积是weight[i-1],价值是value[i-1]。求解将哪些物品装入背包,可使物品重量总和不超过背包容量,且价值总和最大。输出最大价值。算法1:三重循环内层循环用于考虑当前物品i可......
  • 2024年华为OD机试E卷- Boss的收入-(Java&c++&Python)
    题目描述:一个XX产品行销总公司,只有一个b0ss,其有若千一级分销,一级分销又有若干二级分销,每个分错只有唯一的上级分销。规定,每个月,下级分销需要将自己的总收入(自已的+下级上交的)每满100元上交15元给自己的上级现给出一组分销的关系,和每个分销的收入,请找出boss并计算出这个boss......
  • MySQL服务端innodb_buffer_pool_size配置参数
    innodb_buffer_pool_size是什么?innodb_buffer_pool是InnoDB缓冲池,是一个内存区域保存缓存的InnoDB数据为表、索引和其他辅助缓冲区。innodb_buffer_pool_size是这个缓冲池的大小,默认128M(即134217728bytes)。innodb_buffer_pool_size有什么用?如果不设置innodb_buffer_pool_......
  • 20240902_145040 填空题小工具的配置与使用
    收到文件夹配置名称修改config中的name的值不要删双引号启动测试配置题库在数据源目录下新建一个记事本在记事本中输入问题与答案主要的问题与答案由老师提供......
  • Prometheus Alertmanager如何配置externalURL和generatorURL?alermanager webhook
    目录【1】alertmanagerwebhook内容(1)alertmanager.yml配置(2)发送给webhook的内容为json串 在promethes和alertmanager启动的时候,都加上--web.external-url就可以了./prometheus--config.file=prometheus.yml--web.external-url=http://x.x.x.x:9090./alert......
  • 折腾 Quickwit,Rust 编写的分布式搜索引擎-官方配置详解
    Nodeconfiguration(节点配置)节点配置允许您为集群中的各个节点自定义和优化设置。它被分为几个部分:常规配置设置:共享的顶级属性Storage(存储)设置:在storage部分定义https://quickwit.io/docs/configuration/node-config#storage-configurationMetastore(元存储)设置:在metastore......