SGI内存池详解

本文详细介绍了SGI内存池的设计与实现,包括一级配置器和二级配置器。一级配置器提供预申请内存接口,减少内存分配的上下文切换;二级配置器针对小块内存申请,采用内存块分级管理,提高内存申请效率。通过代码示例展示了内存池的工作流程,解释了关键函数如`allocate`、`deallocate`和`reallocate`的实现细节。
摘要由CSDN通过智能技术生成

SGI内存池详解

SGI空间配置器,分为一级配置器和二级配置器,一级配置器对malloc和free(new和delete)做了一次封装,并且开通了预申请的接口。二级配置器,是真正的内存池,减少了内存开辟和释放所进行的上下文切换,提高了内存申请和释放效率。

话不多说,边看代码,边解释。

一级空间配置器

从一级配置器开始,我们顺着思路来,看到哪里说到哪里,先看一下,1号成员

private:
	//函数指针成员,对,他就是释放预申请内存的接口
	static void (*__malloc_alloc_oom_headler)();
	
	typedef void (*OOM_Headler)();	//类型函数指针重命名

看一下他的两个私有方法,oom_malloc、oom_realloc,当我们malloc和realloc堆内存空间不足时,我们释放预申请的内存空间再次申请,如果还申请不到那就只能一首凉凉送给你。

private:
        static void* oom_malloc(size_t n)
        {
            void* result = nullptr;
            void (*my_malloc_handler)() = nullptr;
            for (;;)
            {
                my_malloc_handler = __malloc_alloc_oom_headler;
                if (nullptr == my_malloc_handler)
                {
                    __THROW_BAD_ALLOC; 
                    //打印日志,并退出的宏
                    //#define __THROW_BAD_ALLOC                      \
   						// std::cerr << "out of memory" << std::endl; \
   						// exit(1) 
                }
                
                //my_malloc_handler 意义
                //1. 释放更多的空间 
                //2. 指明新的释放函数 
                //3.没有释放函数也要指明一个结束函数
                (*my_malloc_handler)(); 
                
                result = malloc(n);
                if (nullptr != result)
                {
                    return result;
                }
            }
        }

	    static void* oom_realloc(void* p, size_t new_sz)
        {
            void* result = nullptr;
            void (*my_malloc_handler)() = nullptr;
            for (;;)
            {
                my_malloc_handler = __malloc_alloc_oom_headler;
                if (nullptr == my_malloc_handler)
                {
                    __THROW_BAD_ALLOC;
                }

                (*my_malloc_handler)(); 
               
                result = realloc(p, new_sz);
                if (nullptr != result)
                {
                    return result;
                }
            }
        }

看一下,他对外部所提供的接口,本质是对malloc、realloc进行了封装

allocate,如果malloc 、realloc申请不到内存,调用oom_malloc 进行释放预留空间,再次内存申请

public:
        static void* allocate(size_t n)
        {
            void* result = malloc(n);
            if (nullptr == result)
            {
                result = oom_malloc(n);
            }
            return result;
        }
		static void* reallocate(void* p, size_t old_sz, size_t new_sz)
        {
            void* result = realloc(p, new_sz);
            if (nullptr == result)
            {
                result = oom_realloc(p, new_sz);
            }
            return result;
        }		

deallocate,就是free

 static void deallocate(void* p)
 {
	  free(p);
 }

set_malloc_headller,设置释放预留空间的接口,设置新的返回旧的,我们进行一级配置器演示的Demo的时候,我们就明白他在干什么啦。

public:		
		static OOM_Headler set_malloc_headller(OOM_Headler f)
        {
            OOM_Headler old = __malloc_alloc_oom_headler;
            __malloc_alloc_oom_headler = f;
            return old;
        }
一级空间配置器的Demo
//预申请的两个空间
char* p1 = (char*)malloc(sizeof(char) * 1024 * 1024 * 100);
char* p2 = (char*)malloc(sizeof(char) * 1024 * 1024 * 200);

//release1 和 release2 就是两个释放预审空间的函数,他需要具备一下功能
  //1. 释放更多的空间 
  //2. 指明新的释放函数 
  //3.没有释放函数也要指明一个结束函数
void release2()
{
	free(p2);
	p2 = nullptr;
    //2号 设置3号 or 结束函数
	peter::malloc_alloc::set_malloc_headller(nullptr);
}

void release1()
{
	free(p1);
	p1 = nullptr;
    //1号 设置2号 or 结束函数
	peter::malloc_alloc::set_malloc_headller(release2);
}


int main()
{
    //首先注册1号 释放函数接口
	peter::malloc_alloc::set_malloc_headller(release1);
	int* p = (int*)peter::malloc_alloc::allocate(sizeof(int));
	
	*p = 100;

	peter::malloc_alloc::deallocate(p);
	p = nullptr;
	return 0;
}
二级空间配置器

看了看一级空间配置级,瞬间感觉还是很简单的呀,那么我们再来看看二级配置器。我觉得直接看代码,可能我们不好理解,我们先简单阐述一下,他的原理。在你申请的内存大于128个字节的时候呢,会直接调用一级配置器,当申 请的内存小于128个内存时候,他将其划分为8,16,24,32…128, 15个不同的等级,每个等级下面都链接这大小相等的内存块。

在这里插入图片描述

抽象图,就是这样,比如你想申请,29个字节大小的内存块,内存池就从头部取一个32字节大小的内存块给你,你用完之后又还回来。大概有个了解,我们一起来看代码

    //类型重命名了一级配置器
	typedef __malloc_alloc_template<0> malloc_alloc;
	//设置单位大小为8个字节
    enum
    {
        __ALIGN = 8
    };
	//最大单位块为128
    enum
    {
        __MAX_BYTES = 128
    };
    enum
    {
        //计算出一共又多少块
        __NFREELIST = __MAX_BYTES / __ALIGN
    };

先看他的私有成员

private:
        union obj	//可以理解为链表的node类型
        {
            union obj* free_list_link; // next指针
            char client_data[1];
        };
        static obj* volatile free_list[__NFREELIST]; // 上图的表头
        static char* start_free;	//在池子中,但是还没有挂载表上的内存开始位置
        static char* end_free;		//在池子中,但是还没有挂载表上的内存结束位置
        static size_t heap_size;	//内存池现在,总计内存大小

两个hash函数

        //他在将  1-8 --> 8   
	    //       9-16-->16  
		//       17-24-->24  
        static size_t ROUND_UP(size_t bytes)
        {
            return (bytes + __ALIGN - 1) & ~(__ALIGN - 1);
        }
		//他在将  1-8 --> 8 所对应的下标  0
	    //       9-16-->16 所对应的下标  1
		//       17-24-->24  所对应的下标  2
		//       ..........................
		static size_t FREELIST_INDEX(size_t bytes)
        {
            return (bytes + __ALIGN - 1) / __ALIGN - 1;
        }

下来当然是申请,重新申请,释放函数啦,我们与一个一个欣赏

allocate,对应malloc

public:
        static void* allocate(size_t n)
        {
            if (n > __MAX_BYTES)	//大于128,一级配置器见
            {
                return malloc_alloc::allocate(n);
            }
            obj* volatile* my_free_list = nullptr;
            obj* result = nullptr;

            my_free_list = free_list + FREELIST_INDEX(n);	//定位到内存链位置
            result = *my_free_list;			  //获取内存链的首位
            if (nullptr == result)			//先不考虑,假装他有
            {
                void* r = refill(ROUND_UP(n));
                return r;
            }
            *my_free_list = result->free_list_link;     //链表指向下一个
            return result;		//返回了链表头部内存
        }

deallocate,对应free

	   static void deallocate(void* p, size_t n)
        {
            if (n > (size_t)__MAX_BYTES) //大于128 一级配置器你的活
            {
                return malloc_alloc::deallocate(p);
            }
            obj* q = (obj*)p;//为什么需要强转成obj* 类型,因为有他,才有next指针
            obj* volatile* my_free_list = free_list + FREELIST_INDEX(n);
     		//链表头插法,大家不陌生吧,把free_list_link看成next
            q->free_list_link = *my_free_list;
            *my_free_list = q;
        }

reallocate,对应realloc

        static void* reallocate(void* p, size_t old_sz, size_t new_sz)
        {
            //一级配置器的活
            if (old_sz > __MAX_BYTES && new_sz > __MAX_BYTES)
            {
                return malloc_alloc::reallocate(p, old_sz, new_sz);
            }
        //举个例子 原来是 7 现在要申请 8 ,我原来就给了你8字节大小,不用折腾了还是这块
            if (ROUND_UP(old_sz) == ROUND_UP(new_sz))
            {
                return p;
            }
            //sz 获取新旧最小的大小
            size_t sz = old_sz > new_sz ? new_sz : old_sz;
            //申请新的内存块
            void* q = allocate(new_sz);
            //以最小为基准,移动内容
            memmove(q, p, sz);
            //返还旧的空间到内存池
            deallocate(p, old_sz);
            return q;
        }

有没有感受到效率倍增。

下来我们看一下refill、chunk_alloc这两个私有函数,他们是干什么的呢?

1、refill:完成链表的创建
2、chunk_alloc:向池子中注入水(内存)

先看refill,问题:

1、我要申请20个8字节大小的内存块,申请不到怎么办
2、如果申请不到20个,能申请多少个

    static void* refill(size_t n)
    {
        int nobjs = 20;		//如果链表中没有内存块了,默认我直接给他申请20个
        //chunk_alloc 的 nobjs是一个引用传递,标志这最终能够申请多少块
        char* chunk = chunk_alloc(n, nobjs);
        //只能申请一块,返回就好了
        if (1 == nobjs)
        {
            return chunk;
        }
        //返回多个呢,我将从第二个内存块开始,全部链接起来挂在表上,n表示内存块的大小
        //没有理解的话,看下图吧
        for (int i = 1; i < nobjs - 1; i++)
        {
            obj* q = (obj*)(chunk + n * i);
            q->free_list_link = (obj*)(chunk + n * (i + 1));
        }
        obj* q = (obj*)(chunk + n * (nobjs - 1));
        q->free_list_link = nullptr;
        obj* volatile* my_free_list = free_list + FREELIST_INDEX(n);
        *my_free_list = (obj*)(chunk + n);
        
        //返回第一个
        return chunk;
    }

在这里插入图片描述

chunk_alloc,向池子中注水,问题

1、怎么注,什么时候注、注多少,
2、申请不到水怎么办

如果没有看懂注释,结合这图一起看吧

	static char* chunk_alloc(size_t size, int& nobjs)
    {
        char* result = nullptr;
        size_t total_bytes = size * nobjs;//需求注入
        size_t bytes_left = end_free - start_free;	//现存水量
        if (bytes_left >= total_bytes)	//有水 
        {
            result = start_free;
            start_free = start_free + total_bytes;	//start_free 下降
            return result;			//返回内存,交给refill,组织链接
        }
        else if (bytes_left > size)		//水不足,但是至少够一个  如图 情况二
        {
            nobjs = bytes_left / size;		//实际能满足多少个
            total_bytes = size * nobjs;		//实际大小
            result = start_free;			
            start_free = start_free + total_bytes; //start_free 下降
            return result;	 	 //返回内存,交给refill,组织链接
        }
        else				//没水,或者水连一个都不够
        {
            //申请水,需求的2倍 外加一个偏移量
            size_t bytes_to_get = total_bytes * 2 + ROUND_UP(heap_size >> 4);
            if (bytes_left > 0)		//还有点水,挂到对应的表下,比如剩下24 就挂在2号表格下
            {
                obj* volatile* my_free_list = free_list + 
                                           (FREELIST_INDEX(bytes_left));
                ((obj*)start_free)->free_list_link = *my_free_list;
                *my_free_list = ((obj*)start_free);
            }
            //准备注水,标记归位
            start_free = end_free = nullptr;
            // start_free = (char *)malloc(bytes_to_get);
            //调用一级配置器申请,问题 申请不到怎么办  参考如同情况三
            start_free = (char*)malloc_alloc::allocate(bytes_to_get);
            if (nullptr == start_free)	//申请不到
            {
                obj* volatile* my_free_list = nullptr;
                obj* p = *my_free_list;
                //遍历所有比他大的表头区域,如果存在,取一块出来,先用用(这里取出来就不会还了)
                for (int i = size; i < __MAX_BYTES; i += __ALIGN)
                {
                    my_free_list = free_list + FREELIST_INDEX(i);
                    obj* p = *my_free_list;
                    if (p != nullptr)
                    {
                        *my_free_list = p->free_list_link;
                        start_free = (char*)p;
                        end_free = start_free + i;
                        //借到一块,重新看一下吧,有水了
                        //真走到这里,有点危险了
                        return chunk_alloc(size, nobjs);
                    }
                }
            }
            //一级配置器 申请到水了,全部注入
            end_free = start_free + bytes_to_get;
            //池子大小增加
            heap_size += bytes_to_get;
            //重新探测,已经注入水源
            return chunk_alloc(size, nobjs);
        }
    }

情况一,从无到有,分配完成之后还有剩余

在这里插入图片描述

情况二,至少够一个
在这里插入图片描述

情况三,不足一个,且没有一级配置器无法申请到内存

在这里插入图片描述

二级空间配置器Demo

通过Debug,我们可以看到其中所有过程

int main()
{
	int n = 15;
	int tag;
	while (cin >> tag, tag != -1)
	{
		char* p = (char*)peter::alloc::allocate(n);
	}

	n = 24;
	while (cin >> tag, tag != -1)
	{
		char* p = (char*)peter::alloc::allocate(n);
	}
	return 0;
}

参考文献
·《STL源码剖析》 侯捷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Yi_Xiao

来瓶可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值