目录

1.内存池的工作原理

2.内存池的优势

3.长时间运行的应用和内存碎片

4.代码实践 

4.1核心结构(内存池结构)

4.2核心函数 

4.2.1创建内存池

4.2.2销毁内存池 

 4.2.3分配内存块

 4.2.4归还内存块入池


内存池(Memory Pool)是一种高效的内存管理策略,旨在优化内存分配效率、减少内存碎片,并提高应用程序性能。它特别适合需要频繁分配和释放小块内存的场景,如高性能计算、实时系统、长时间运行的服务器和嵌入式设备等。

1.内存池的工作原理

内存池的基本思想是在程序启动或在需要之前,预先从操作系统中分配一大块连续的内存空间。这块内存随后根据应用需求被划分成若干小块,用以满足程序运行中的内存请求。

  1. 预分配:内存池通过一次性申请一大块内存,避免了频繁向操作系统申请和释放内存的开销。

  2. 内存分配:当应用程序请求内存时,内存池管理器会从已分配的大块内存中切割出所需大小的内存块,并快速响应内存请求。

  3. 内存回收:释放的内存不返回给操作系统,而是标记为可再次分配,留在内存池中供后续请求使用。

  4. 内存释放:当内存池不再需要时(如程序终止),整个内存池将被一次性释放回操作系统。

2.内存池的优势

  • 提升性能:减少了操作系统的内存分配调用,加快内存分配速度,提高程序运行效率。
  • 减少内存碎片:预分配和固定大小的分配策略可以有效减少外部内存碎片的产生。此外,由于内存块的重新利用,内部碎片也得到有效控制。
  • 增强可预测性:内存分配时间变得可预测,对于实时系统尤为重要,因为它们对系统响应时间有严格要求。

3.长时间运行的应用和内存碎片

长时间运行的软件,尤其是那些需要频繁进行小块内存操作的应用,容易产生内存碎片。内存碎片不仅会降低系统性能,还可能导致内存使用效率降低,甚至引发内存分配失败的错误。

内存池通过管理预分配的内存块来有效减少碎片,特别是外部碎片。内存池的固定分配策略意味着每次分配和回收的内存块大小是一致的,这有助于保持内存布局的整洁和连续,从而减少由于内存块大小不一致而导致的空间浪费。

4.代码实践 

4.1核心结构(内存池结构)

typedef struct mempool_s {
	int blocksize;
	int freecount;
	char *free_ptr;
	char *mem;
} mempool_t; 

mempool_t:定义了内存池的结构体,包含以下字段:

  • blocksize:内存块的大小。
  • freecount:当前可用的内存块数量。
  • free_ptr:指向当前可用内存块的指针。
  • mem:指向内存池起始位置的指针。
4.2核心函数 
4.2.1创建内存池
// 2^n, page_size 4096, block_size: 16, 32, 64, 128 
int memp_create(mempool_t *m, int block_size) {

	if (!m) return -1;

	m->blocksize = block_size;
	m->freecount = MEM_PAGE_SIZE / block_size;

	m->mem = (char *)malloc(MEM_PAGE_SIZE); //
	if (!m->mem) {  //NULL
		return -2;
	}
	memset(m->mem, 0, MEM_PAGE_SIZE); // 

	m->free_ptr = m->mem;

	int i = 0;
	char *ptr = m->mem;
	for (i = 0;i < m->freecount;i ++) {
		*(char **)ptr = ptr + block_size;
		ptr = ptr + block_size;
	} 
	*(char **)ptr = NULL;

	return 0;
}
  • 初始化内存池,分配一整页内存,并根据指定的块大小进行初始化。
  • 参数block_size指定了每个内存块的大小。
  • 通过链接空闲内存块来创建一个空闲列表,每个块的开始存储了指向下一个空闲块的指针。
  • 返回0表示成功,-1表示传入的指针为空,-2表示内存分配失败。

for (i = 0;i < m->freecount;i ++) {
		*(char **)ptr = ptr + block_size;
		ptr = ptr + block_size;
	} 

用于设置内存池中每个内存块的链接指针。具体来说,它通过循环设置每个内存块的起始部分存储指向下一个内存块的指针,从而形成一个空闲块链表。这里详细解释一下这段代码的运作机制:

  1. 循环控制

    • for (i = 0; i < m->freecount; i++):这个循环遍历每个内存块,m->freecount 表示总共有多少个内存块可以被分配,这个数是根据内存页的总大小除以每个块的大小计算得出。
  2. 设置链接指针

    • *(char **)ptr = ptr + block_size:这行代码实现了链表的链接功能。
      • ptr:当前内存块的起始地址。
      • ptr + block_size:指向下一个内存块的起始地址。
      • *(char **)ptr:将 ptr 转换为 char ** 类型,即指向 char * 的指针,然后解引用它以存储下一个块的地址。这样,当前内存块的开始几个字节(通常是4或8字节,取决于平台的指针大小)被用来存储下一个内存块的地址。
  3. 移动到下一个块

    • ptr = ptr + block_size:更新 ptr 以指向下一个内存块的起始位置,准备下一次循环的链接指针设置。

总结

主要功能是初始化内存池中的空闲链表。通过在每个块的开始设置一个指向下一个空闲块的指针,当调用 memp_alloc 函数时,可以快速地从链表头部获取一个空闲块,并通过更新头指针到下一个空闲块来管理空闲块的分配。同样地,memp_free 函数也可以通过简单地将释放的块插回到链表的头部来回收内存块。

4.2.2销毁内存池 
void memp_destory(mempool_t *m) {

	if (!m) return ;

	free(m->mem);

}
  • 销毁内存池,释放分配的内存。
  • 只需要释放内存池开始的指针即可,因为所有的内存块都在同一内存页中。
 4.2.3分配内存块
void *memp_alloc(mempool_t *m) {

	if (!m || m->freecount == 0) return NULL;

	void *ptr = m->free_ptr;

	m->free_ptr = *(char **)ptr;
	m->freecount --;
	
	return ptr;
}
  • 从内存池中分配一个内存块。
  • 更新free_ptr到下一个空闲块,并减少freecount
  • 返回指向分配内存块的指针。
 4.2.4归还内存块入池
void memp_free(mempool_t *m, void *ptr) {

	*(char **)ptr = m->free_ptr;
	m->free_ptr = (char *)ptr;

	m->freecount ++;
}
  • 将一个内存块归还到内存池中。
  • 通过修改归还块的指针将其链接到空闲列表的头部,更新free_ptr指向归还的内存块,增加freecount

参考:

0voice · GitHub

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐