SGI STL二级空间配置器源码剖析(2)

news2024/9/22 15:34:24

接着上回,这节开始说allocte内存分配的实现

目录

allocate源码流程:

_S_refill 的实现:

_S_chunk_alloc的实现:

deallocate:

reallocate:


二级空间配置器的逻辑步骤:
假如现在申请n个字节:
1、判断n是否大于128,如果大于128则直接调用一级空间配置器。如果不大于,则将n上调至8的倍数处,然后再去自由链表中相应的结点下面找,如果该结点下面挂有未使用的内存,则摘下来直接返回这块空间的地址。否则的话我们就要调用refill(size_t n)函数去内存池中申请。

2、向内存池申请的时候可以多申请几个,STL默认一次申请nobjs=20个,将多余的挂在自由链表上,这样能够提高效率。
  进入refill函数后,先调chunk_alloc(size_t n,size_t& nobjs)函数去内存池中申请,如果申请成功的话,再回到refill函数。
  这时候就有两种情况,如果nobjs=1的话则表示内存池只够分配一个,这时候只需要返回这个地址就可以了。否则就表示nobjs大于1,则将多余的内存块挂到自由链表上。
  如果chunk_alloc失败的话,在他内部有处理机制。

3、进入chunk_alloc(size_t n,size_t& nobjs )向内存池申请空间的话有三种情况:
  3.1、内存池剩余的空间足够nobjs*n这么大的空间,则直接分配好返回就可以了。
  3.2、内存池剩余的空间leftAlloc的范围是n<=leftAlloc<nobjs*n,则这时候就分配nobjs=(leftAlloc)/n这么多个的空间返回。
  3.3、内存池中剩余的空间连一个n都不够了,这时候就要向heap申请内存,不过在申请之前先要将内存池中剩余的内存挂到自由链表上,之后再向heap申请。
   3.3.1、如果申请成功的话,则就再调一次chunk_alloc重新分配。
   3.3.2、如果不成功的话,这时候再去自由链表中看看有没有比n大的空间,如果有就将这块空间还给内存池,然后再调一次chunk_alloc重新分配。
   3.3.3、如果没有的话,则就调用一级空间配置器分配,看看内存不足处理机制能否处理。
 

allocate源码流程:

static void* allocate(size_t __n)
  {
    void* __ret = 0;
	//如果申请大于128字节了,就不受内存池管了
    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }//通过malloc去开辟
    else {
		//小于等于,通过内存池去开辟分配了
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_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
      _Obj* __RESTRICT __result = *__my_free_list;
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };

 整个流程:利用栈上对象出作用域自动析构的特点,构造析构加锁解锁,完全支持线程安全

_S_refill 的实现:

底层分配内存的接口

template <bool __threads, int __inst>
void*
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)
{
    int __nobjs = 20;
    char* __chunk = _S_chunk_alloc(__n, __nobjs);
    _Obj* __STL_VOLATILE* __my_free_list;
    _Obj* __result;
    _Obj* __current_obj;
    _Obj* __next_obj;
    int __i;

    if (1 == __nobjs) return(__chunk);
    __my_free_list = _S_free_list + _S_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 -> _M_free_list_link = 0;
            break;
        } else {
            __current_obj -> _M_free_list_link = __next_obj;
        }
      }
    return(__result);
}

以要开辟8字节内存为例:

让__my_free_list指向对应下标位置,让result指向了链表chunk块头,一次移动一块,让头指针往下移

_S_refill 就干了两件事,一件是从内存池分配了每条带20个结点的链表,另一件就是根据指针移动分配内存结点,把各个chunk内存块连接起来

下面就是通过_S_chunk_alloc去从内存池里分配一块一块的chunk

_S_chunk_alloc的实现:

template <bool __threads, int __inst>
char*
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size, 
                                                            int& __nobjs)
{
    char* __result;
    size_t __total_bytes = __size * __nobjs;//内存池要分配的字节数8*20 ,20个结点
    size_t __bytes_left = _S_end_free - _S_start_free;//剩余的字节数,0,320

    if (__bytes_left >= __total_bytes) {//chunk够用,递归第二遍开始走320>160
        __result = _S_start_free; //如果剩余的大小大于等于申请的大小,则返回这个这
        _S_start_free += __total_bytes;//直接加到160,40个结点分两半
        return(__result);
		
    } else if (__bytes_left >= __size) {
		//如果剩余的内存足够分配一个新size, 比如8字节链表下还有20块,现在要分配16字节的
		//8*20=160  刚好能分配10个16字节,这样的话就把这边8字节的块合成10块当16字节分配了
		//然后把这些chunk块写到16字节编号下面
		//等于分配的40个字节,前20个给8字节,后20备用,如果需要16,32...字节就按剩下的块整合
		//按照新字节大小分配出去,把整合剩的挂到新size编号上
		__nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);
		
    } else { //内存池中的内存已经不够一个size了,一开始走这
        size_t __bytes_to_get =                       //最开始开辟40个结点
	  2 * __total_bytes + _S_round_up(_S_heap_size >> 4);//0>>4 上调字节
        // Try to make use of the left-over piece.
        if (__bytes_left > 0) {
            _Obj* __STL_VOLATILE* __my_free_list =
                        _S_free_list + _S_freelist_index(__bytes_left);

            ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list;
            *__my_free_list = (_Obj*)_S_start_free;
        }
        _S_start_free = (char*)malloc(__bytes_to_get);//320
        if (0 == _S_start_free) {//如果从内存池中申请开辟失败
            size_t __i;
            _Obj* __STL_VOLATILE* __my_free_list;
	    _Obj* __p;
            // Try to make do with what we have.  That can't
            // hurt.  We do not try smaller requests, since that tends
            // to result in disaster on multi-process machines.
            for (__i = __size;
                 __i <= (size_t) _MAX_BYTES;//走到128
                 __i += (size_t) _ALIGN) {  //一次走8
                __my_free_list = _S_free_list + _S_freelist_index(__i);
                __p = *__my_free_list;//遍历编号,走到能分配的位置>=当前size,去分配chunk
                if (0 != __p) {
					//比如要分配40字节,40上也不够了,就走到48上去,56上去..
                    *__my_free_list = __p -> _M_free_list_link;//谁有谁分配
                    _S_start_free = (char*)__p;
                    _S_end_free = _S_start_free + __i;
                    return(_S_chunk_alloc(__size, __nobjs));
                    // Any leftover piece will eventually make it to the
                    // right free list.
                }
            }     
				 //找不到能分配的就malloc从堆往内存池申请
	    _S_end_free = 0;	// In case of exception.
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);
            // This should either throw an
            // exception or remedy the situation.  Thus we assume it
            // succeeded.
        }
        _S_heap_size += __bytes_to_get;
        _S_end_free = _S_start_free + __bytes_to_get;//走完一遍,__bytes_left就有值了
        return(_S_chunk_alloc(__size, __nobjs));//递归调用
    }
}

如果没有分配,就一上来分配20个结点,再*2,_S_heap_size是0,所以最初一共分配40个,把40个平分两半,把前20个就返回回去,进到_S_refill 里面,把这20个结点连起来,用出去,后20个作为备用

 如果前20各用完了,用到备用20个的时候,要申请大于当前编号字节数的字节时走第二个else if

  • 如果剩余的内存足够分配一个新size, 比如8字节链表下还有20块,现在要分配16字节的
  • 8*20=160  刚好能分配10个16字节,这样的话就把这边8字节的块合成10块当16字节分配了
  • 然后把这些chunk块写到16字节编号下面
  • 等于分配的40个字节,前20个给8字节,后20备用,如果需要16,32...字节就按剩下的块整合
  • 按照新字节大小划分分配出去,把整合剩的挂到新size编号上
 else if (__bytes_left >= __size) {

		__nobjs = (int)(__bytes_left/__size);
        __total_bytes = __size * __nobjs;
        __result = _S_start_free;
        _S_start_free += __total_bytes;
        return(__result);

如果我们备用chunk剩了32字节,现在要分配40字节,我们连一块也分配不出去,此时进到最后一个else

剩的32也不能浪费了,先把这32字节接到对应到能用到的编号下,所以放到了32字节编号下,32字节obj就指向剩的这32字节头,把这32字节和编号下原有的链表串起来了,

 如果当前位置上剩的不够分配,就从当前位置8字节循环为往后找,看哪个位置上有chunk能分配

上述所有操作开辟失败了就malloc从堆申请,这块malloc也是很巧妙的,一直循环去申请直到申请成功

template <int __inst>
void* __malloc_alloc_template<__inst>::_S_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);
    }
}

deallocate:

 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;
	//大于128
    if (__old_sz > (size_t) _MAX_BYTES && __new_sz > (size_t) _MAX_BYTES) {
        return(realloc(__p, __new_sz));
    }
	//如果是小块内存的扩容缩容
    if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);//调整8倍数
    __result = allocate(__new_sz);//如果在不同字节下,重新allocate分配new_sz 
    __copy_sz = __new_sz > __old_sz? __old_sz : __new_sz;//扩容 缩容
    memcpy(__result, __p, __copy_sz);//拷贝
    deallocate(__p, __old_sz);
    return(__result);
}

空间配置器的其他问题:
1、在空间配置器中所有的函数和变量都是静态的,所以他们在程序结束的时候才会被释放发。二级空间配置器中没有将申请的内存还给操作系统,只是将他们挂在自由链表上。所以说只有当你的程序结束了之后才会将开辟的内存还给操作系统。

2、由于它没有将内存还给操作系统,所以就会出现二种极端的情况。
 2.1、假如我不断的开辟小块内存,最后将整个heap上的内存都挂在了自由链表上,但是都没有用这些空间,再想要开辟一个大块内存的话会开辟失败。
 2.2、再比如我不断的开辟char,最后将整个heap内存全挂在了自由链表的第一个结点的后面,这时候我再想开辟一个16个字节的内存,也会失败。
总的来说上面的情况只是小概率情况。如果非得想办法解决的话,我想的是:针对2.1我们可以引入释放二级空间配置器的方法,但是这个释放比较麻烦。针对2.2我们可以合并自由链表上的连续的小的内存块。

3、二级空间配置器会造成内碎片问题,极端的情况下一直申请char,则就会浪费7/8的空间。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196321.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

选择计算机专业,必看的10条自学建议

选择了计算机专业&#xff0c;很迷茫&#xff0c;没事&#xff01;&#xff01;博主整理了关于学习计算机的十条自学经验&#xff0c;从各个方面阐述了如何学习计算机专业。 1、学会使用Google搜索&#xff0c;放弃百度&#xff0c;你会发现Google 会搜出更多有用的答察&#x…

车规级MCU缺货持续2年多,上海航芯持续加码市场

MCU是传统燃油车的重要芯片之一&#xff0c;在电动车领域&#xff0c;MCU也有着广泛的应用&#xff0c;且随着汽车电子化的持续发展&#xff0c;车用MCU的市场规模还将随之持续扩大&#xff0c;据 IC insights 数据显示&#xff0c;至2026年&#xff0c;全球车规级MCU的市场规模…

C++——函数重载,引用

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;补充C语言语法的不足&#…

【Kubernetes】记录一次K8S容器内程序OOM排查过程:unable to create new native thread

文章目录项目背景问题初现问题排查问题定位问题解决项目背景 基于k8s的容器化kafka PaaS管理平台&#xff0c;业务团队申请kafka&#xff0c;通过一系列操作&#xff0c;封装crd&#xff0c;调用operator创建集群&#xff0c;当然还包括其他功能、topic管理、group管理、监控告…

年后上来面试了13家企业软件测试岗位,面试题整理

软件测试面试&#xff0c;800多道高频面试真题&#xff0c;随便刷。&#xff08;希望能帮助大家&#xff09;项目的测试流程 1. 拿到需求文档后&#xff0c;写测试用例 2. 审核测试用例 3. 等待开发包 4. 部署测试环境 5. 冒烟测试&#xff08;网页架构图&#xff09; 6.…

CSS中height:100vh和height:100%的区别是什么?

CSS中height:100vh和height:100%的区别 首先&#xff0c;我们得知道1vh它表示的是当前屏幕可见高度的1/100&#xff0c;而1%它表示的是父元素长或者宽的1%&#xff08;可以这么理解&#xff1f;&#xff09; 1、对于设置height:100%;有下面几种情况&#xff1a; &#xff08…

如何使用Maven快速构建JavaWeb项目?在idea中使用TomCat详细解读

文章目录1. 前言2. Web项目的结构3. 创建Maven Web项目4. 在IDEA中使用TomCat4.1 集成本地TomCat4.2 使用TomCat Maven插件5. 总结&#x1f4c2;橙子精品文章学习推荐1. 前言 前面在 Web 服务器 TomCat 快速入门一文中&#xff0c;我们介绍了 Web 服务器的基本概念以及 TomCat…

工业平板电脑实现工厂自动化设备无需手动连接

随着中国经济的快速发展和材料水平的不断提高&#xff0c;制造业的竞争日益激烈&#xff0c;市场竞静力逐渐转向质量、效率和价格服务&#xff0c;制造业企业面临更大的挑战&#xff0c;数据转型迫在眉睫。对工业平板电脑的需求也在增加&#xff0c;面向行业的工业平板电脑已成…

Java设计模式--工厂模式

目录 1.简单工厂模式 1.1类图 1.2 代码示例 2.工厂方法模式 2.1 类图 2.2 代码示例 3.抽象工厂模式 3.1 类图 3.2 代码示例 实际应用&#xff1a; 总结&#xff1a; 1.简单工厂模式 定义了一个创建对象的类&#xff0c;由这个类来封装实力化对象的行为。 1.1类图 1.…

《三体》中罗辑所说的定位行星的位置,是怎样实现的?

最近流浪地球2&#xff0c;三体电视剧火得一塌糊涂&#xff0c;《三体》中罗辑用咒语标记了三体星系位置&#xff0c;利用黑暗森林理论与三体人对峙长达两百年&#xff0c;那么这种定位技术在现实中是否存在呢&#xff1f;咒语标记三体星系位置这件事&#xff0c;听起来很玄乎但…

vite兼容chrome48的方法

chrome48不支持async await语法&#xff0c;但有些桌面客户端的内嵌浏览器就是chrome48,如下操作即可兼容 当前环境&#xff1a;2023-2-3使用npm create vitelatest创建 开始兼容操作 安装vite推荐的 vitejs/plugin-legacy 文档官网 https://github.com/vitejs/vite/tree/m…

【JavaEE】HTTP的方法、报头、状态码

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【JavaEE】 ✈️✈️本篇内容:http请求的方法、报头&#xff1b;状态码&#xff01; &#x1f680;&#x1f680;代码存放仓库gitee&#xff1a;JavaEE代码&#…

学习QCustomPlot【4】库官方examples之plots解读

文章目录一、前言二、案例解说0&#xff1a;Quadratic Demo【二次曲线demo】1、Simple Demo【简单demo】2、Sinc Scatter Demo【Sinc函数散点demo】3、Scatter Style Demo【散点样式demo】4、Line Style Demo【线型demo】5、Scatter Pixmap Demo【图标散点demo】6、Date Demo【…

RANSAC的实现与应用

一、前言RANSAC(Random Sample Consensus)算法并不陌生&#xff0c;在上一篇博客中&#xff08;基于SIFT的图像Matlab拼接教程&#xff09;也提到过&#xff0c;之前代码中也多次用过&#xff0c;其在直(曲)线拟合、特征匹配、过滤外点(Outlier)等领域有着重要的应用。RANSAC出…

线性代数之线性基

在谈论线性基之前,先介绍什么是基向量. 根据高中数学,一个二维直角平面坐标系中的所有向量都可以只用(0, 1)和(1, 0)合成.那么(0, 1)和(1, 0)就是基向量,所有基向量能合成的所有向量被称为基向量的张成空间. 在二维空间中,有没有其他的向量能作为基向量呢?答案是肯定的. 上图…

Oracle事務簡述

簡述本文主要介紹內容有事務的隔離級別&#xff0c;oracle支持的事務隔離級別&#xff0c;事務的提交與回滾&#xff0c;保存點內容事務的ACID特征介紹事務繞不過事務的ACID四個特征&#xff0c;這裡簡單回顧以下原子性&#xff08;Atomicity&#xff09;事務的執行要麼全部成功…

广义霍夫变换和模板匹配的不同

简述说到霍夫变换&#xff0c;做图像的知道经典霍夫变换最常用于检测规则曲线&#xff0c;如直线、圆、椭圆等。而广义霍夫变换是为了检出那些无法写出解析式的不规则形状&#xff0c;虽然在深度学习大行其道的时代&#xff0c;霍夫变换也还是有很多应用场景&#xff0c;另外广…

2023年黑马Java入门到精通教程--面向对象

推荐教程&#xff1a;java零基础入门到精通 面向对象编程的例子 设计类&#xff0c;创建对象并使用 1. 类和对象是什么&#xff1f; 类&#xff1a;是共同特征的描述(设计图)&#xff1b;对象&#xff1a;是真实存在的具体实例。 2. 如何设计类&#xff1f; 3. 如何创建对象…

CISP-PTE-Windows2003教程

为方便后续操作&#xff0c;建议和kali在同一网段。 获取到靶机IP后&#xff0c;扫描端口&#xff0c;1433是sqlserver的 测出用户名admin&#xff0c;但是密码爆破失败 扫描目录发现配置文件 配置文件中找到数据库的用户名和密码 使用Microsoft SQL Server Studio连接&#x…

MySQL从入门到精通(第0篇):全程有动画演示,适合入门学习

B站地址 文章目录一、MySQL的系统框架1. 连接池1.1 连接模块1.2 连接池2. SQL接口、SQL解析器、SQL优化器3. 存储引擎二、MySQL数据写入原理三、MySQL存储结构1. 使用InnoDB创建表2. 详述ibd文件中的存储结构2.1 页的数据连续存储2.2 行的结构2.3 区的结构2.4 组的结构2.5 段的…