2024年鸿蒙最全性能优化-高效内存池的设计与实现_内存池的设计和实现(2),2024年最新京东鸿蒙面试题

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

上述只是一个简单的逻辑讲解,比较宏观,下面我们将通过图解和代码的方式来进行讲解。

设计

在上图中,我们画出了内存池的结构图,从图中,可以看出,有两个结构变量,分别为MemoryPool和MemoryBlock。

下面我们将从数据结构和接口两个部分出发,详细讲解内存池的设计。

数据结构
MemoryBlock

本文中所讲述的内存块的分配和释放都是通过该结构进行操作,下面是MemoryBlock的示例图:

MemoryBlock

在上图中,Header存储该MemoryBlock的内存块情况,比如可用的内存块索引、当前MemoryBlock中可用内存块的个数等等。

定义如下所示:

struct MemoryBlock {
	unsigned int size;
	unsigned int free_size;
	unsigned int first_free;

	struct MemoryBlock \*next;
	char a_data[0];	
};

其中:

  • size为MemoryBlock下内存块的个数
  • free_size为MemoryBlock下空闲内存块的个数
  • first_free为MemoryBlock中第一个空闲块的索引
  • next指向下一个MemoryBlock
  • a_data是一个柔性数组

柔性数组即数组大小待定的数组, C语言中结构体的最后一个元素可以是大小未知的数组,也就是所谓的0长度,所以我们可以用结构体来创建柔性数组。

它的主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题。

MemoryPool

MemoryPool为内存池的头,里面定义了该内存池的信息,比如本内存池分配的固定对象的大小,第一个MemoryBlock等

struct MemoryPool {
	unsigned int obj_size;
	unsigned int init_size;
	unsigned int grow_size;

	MemoryBlock *first_block;
};

其中:

  • obj_size为内存池分配的固定内存块的大小
  • init_size初始化内存池时候创建的内存块的个数
  • grow_size当初始化内存块使用完后,再次申请内存块时候的个数
  • first_block指向第一个MemoryBlock
接口
memory_pool_create
MemoryPool \*memory\_pool\_create(unsigned int init_size, 
                               unsigned int grow_size, 
                               unsigned int size);

本函数用来创建一个MemoryPool,并对其进行初始化,下面是参数说明:

  • init_size 表示第一个MemoryBlock中创建块的个数
  • grow_size 表示当MemoryPool中没有空闲块可用,则创建一个新的MemoryBlock时其块的个数
  • size 为块的大小(即每次分配相同大小的固定size)
memory_alloc
void \*memory\_alloc(MemoryPool \*mp);

本函数用了从mp中申请一块内存返回

  • mp 为MemoryPool类型指针,即内存池的头
  • 如果内存分配失败,则返回NULL
memory_free
 void\* memory\_free(MemoryPool \*mp, void \*pfree);

本函数用来释放内存

  • mp 为MemoryPool类型指针,即内存池的头
  • pfree 为要释放的内存
free_memory_pool
void free\_memory\_pool(MemoryPool \*mp);

本函数用来释放内存池

实现

在讲解整个实现之前,我们先看先内存池的详细结构图。

初始化内存池

MemoryPool是整个内存池的入口结构,该函数主要是用来创建MemoryPool对象,并使用参数对其内部的成员变量进行初始化。

函数定义如下:

MemoryPool \*memory\_pool\_create(unsigned int init_size, unsigned int grow_size, unsigned int size)
{
	MemoryPool \*mp;
	mp = (MemoryPool\*)malloc(sizeof(MemoryPool));
	mp->first_block = NULL;
	mp->init_size = init_size;
	mp->grow_size = grow_size;

	if(size < sizeof(unsigned int))
		mp->obj_size = sizeof(unsigned int);
	mp->obj_size = (size + (MEMPOOL_ALIGNMENT-1)) & ~(MEMPOOL_ALIGNMENT-1);

	return mp;
}

内存分配
void \*memory\_alloc(MemoryPool \*mp) {

	unsigned int i;
	unsigned int length;

	if(mp->first_block == NULL) {
		MemoryBlock \*mb;
		length = (mp->init_size)\*(mp->obj_size) + sizeof(MemoryBlock);
		mb = malloc(length);
		if(mb == NULL) {
			perror("memory allocate failed!\n");
			return NULL;
		}

		/\* init the first block \*/
		mb->next = NULL;
		mb->free_size = mp->init_size - 1;
		mb->first_free = 1;
		mb->size = mp->init_size\*mp->obj_size;

		mp->first_block = mb;
		
		char \*data = mb->a_data;

		/\* set the mark \*/
		for(i=1; i<mp->init_size; ++i) {
			\*(unsigned long \*)data = i;
			data += mp->obj_size;
		}

		return (void \*)mb->a_data;
	}

	MemoryBlock \*pm_block = mp->first_block;

	while((pm_block != NULL) && (pm_block->free_size == 0)) {
		pm_block = pm_block->next;
	}

	if(pm_block != NULL) {
		char \*pfree = pm_block->a_data + pm_block->first_free \* mp->obj_size;

		pm_block->first_free = \*((unsigned long \*)pfree);
		pm_block->free_size--;

		return (void \*)pfree;
	} else {
		if(mp->grow_size == 0)
			return NULL;
		
    MemoryBlock \*new_block = (MemoryBlock \*)malloc((mp->grow_size)\*(mp->obj_size) + sizeof(MemoryBlock));

		if(new_block == NULL)
			return NULL;

		char \*data = new_block->a_data;

		for(i=1; i<mp->grow_size; ++i) {
			\*(unsigned long \*)data = i;
			data += mp->obj_size;
		}		

		new_block->size = mp->grow_size\*mp->obj_size;
		new_block->free_size = mp->grow_size-1;
		new_block->first_free = 1;
		new_block->next = mp->first_block;
		mp->first_block = new_block;
		
		return (void \*)new_block->a_data;
	}
}

内存块主要在MemoryBlock结构中,也就是说申请的内存,都是从MemoryBlock中进行获取,流程如下:

  • 获取MemoryPool中的first_block指针
    • 如果该指针为空,则创建一个MemoryBlock,first_block指向新建的MemoryBlock,并返回
    • 否则,从first_block进行单链表遍历,查找第一个free_size不为0的MemoryBlock,如果找到,则对该MemoryBlock的相关参数进行设置,然后返回内存块
    • 否则,创建一个新的MemoryBlock,进行初始化分配之后,将其插入到链表的头部(这样做的目的是为了方便下次分配效率,即减小了链表的遍历)

在上述代码中,需要注意的是第30-33行或者67-70行,这两行的功能一样,都是对新申请的内存块进行初始化,这几行的意思,是要将空闲块连接起来,但是,并没有使用传统意义上的链表方式,而是通过index方式进行连接,具体如下图所示:

在上图中,第0块空闲块的下一个空闲块索引为1,而第1块空闲块的索引为2,依次类推,形成了如下链表方式

1->2->3->4->5

内存分配流程图如下所示:

内存释放
void\* memory\_free(MemoryPool \*mp, void \*pfree) {
	if(mp->first_block == NULL) {
    return;
  }

	MemoryBlock \*pm_block = mp->first_block;
	MemoryBlock \*pm_pre_block = mp->first_block;
	
	/\* research the MemoryBlock which the pfree in \*/
	while(pm_block && ((unsigned long)pfree < (unsigned long)pm_block->a_data || 
		(unsigned long)pfree>((unsigned long)pm_block->a_data+pm_block->size))) {
		//pm\_pre\_block = pm\_block;
		pm_block = pm_block->next;

		if(pm_block == NULL) {
      return pfree;
    }
	}

	unsigned int offset = pfree -(void\*) pm_block->a_data;

	if((offset&(mp->obj_size -1)) > 0) {
    return pfree;
  }

	pm_block->free_size++;
	\*((unsigned int \*)pfree) = pm_block->first_free;

	pm_block->first_free=(unsigned int)(offset/mp->obj_size);

	return NULL;
}

内存释放过程如下:

  • 判断当前MemoryPool的first_block指针是否为空,如果为空,则返回

  • 否则,遍历MemoryBlock链表,根据所释放的指针参数判断是否在某一个MemoryBlock中

    • 如果找到,则对MemoryBlock中的各个参数进行操作,然后返回
    • 否则,没有合适的MemoryBlock,则表明该被释放的指针不在内存池中,返回

在上述代码中,需要注意第20-29行。

  • 第20行,求出被释放的内存块在MemoryBlock中的偏移
  • 第22行,判断是否能被整除,即是否在这个内存块中,算是个double check
  • 第26行,将该MemoryBlock中的空闲块个数加1
  • 第27-29行,类似于链表的插入,将新释放的内存块的索引放入链表头,而其内部的指向下一个可用内存块

现在举个例子,以便于理解,假设在一开始有5个空闲块,其中前三个空闲块都分配出去了,那么此时,空闲块链表如下

4->5,其中first_free = 4

然后在某一个时刻,第1块释放了,那么释放归还之后,如下

1->4->5,其中first_free = 1

内存释放流程图如下:

内存释放

释放内存池
void free\_memory\_pool(MemoryPool \*mp) {
	MemoryBlock \*mb = mp->first_block;

	if(mb != NULL) {
		while(mb->next != NULL) {
			s_memory_block \*delete_block = mb;


![img](https://img-blog.csdnimg.cn/img_convert/abc3dd8e3eb6de838c408e2e05157779.png)
![img](https://img-blog.csdnimg.cn/img_convert/467bddb39bed246084fbbcc66e3a5b3e.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

first_block;

	if(mb != NULL) {
		while(mb->next != NULL) {
			s_memory_block \*delete_block = mb;


[外链图片转存中...(img-HzCRMTuG-1715732222312)]
[外链图片转存中...(img-whXsMMtR-1715732222312)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/topics/618636735)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值