内存池原理及设计与实现
什么是内存池?
内存池就是将malloc和free进行封装,为了减少内存碎片、加快内存分配而设计出的一种内存分配方式。
内存池原理
伙伴算法
将内存以2的次方形式进行划分,2、4、8、16、32、64……,将申请分配的空间以最近的数值分配(大于申请空间),这种形式可以尽量减少内存碎片,浪费少量内存。 以下列方式实现内存块的覆盖:
- 将大块内存(
2
n
2^n
2n)分为两个小块(
2
n
?
1
2^{n-1}
2n?1)内存
- 将两块物理连续的内存(
2
n
?
1
2^{n-1}
2n?1)合并为一个大块内存(
2
n
2^n
2n)。
链表
这种方法就是使用链表将页连起来,对页进行管理。(释放时机不好掌控,释放位置不好确定,耗费内存)
- 申请的空间大于等于页,则判定为大块内存,单独开辟存入大块内存链表;
- 申请的空间小于页,则从现有页中进行分配(直接给出地址);
我设计的内存池
伙伴算法
- 每个
2
n
2^n
2n级都创建一个链表进行管理,每个链表按照地址升序进行排序,每一个结构体中存储begin和end来表示该内存区间,end大于实际存储空间(相当于下一个空间的begin),这样方便小块合并。
- 申请空间先在最近队列中寻找有无空闲节点,没有则依次向大一级队列拆分空间,到页还找不到则开辟空间,将申请的地址和结构体地址合并为key、value存放进红黑树,可用C++map。
- 释放空间在红黑树中查找到对应的结构体,然后进行结构体设置,依次左右查看是否能合并。
链表
数据结构
- 大块内存结构体
typedef struct _MEMORY_LARGE { struct _MEMORY_LARGE *next; unsigned char data[0]; } MEMORY_LARGE;
- 下一个节点
- 柔性数组(0长数组),存储实际数据。
- 页结构体
typedef struct _MEMORY_PAGE { int used; void *current; // current void *end; // rear + 1, address can’t use struct _MEMORY_PAGE *prev; struct _MEMORY_PAGE *next; unsigned char data[0]; } MEMORY_PAGE;
- 被使用了几次
- 当前页未使用空间的起始地址
- 当前页的结束地址(大于实际可使用地址,+1)
- 上一页
- 下一页
- 存储实际数据。
- 内存池结构体
typedef struct _MEMORY_POOL { #ifdef INFO_FLAG FILE *info_fd; #endif int page_try; int page_num; int page_idle_num; int large_num; struct _MEMORY_PAGE *page_head; struct _MEMORY_PAGE *page_rear; struct _MEMORY_PAGE *page_current; struct _MEMORY_LARGE *large_head; } MEMORY_POOL;
- 信息文件fd
- 当前页尝试申请了几次空间(不成功),用于跳过当前页(3次不成功就不在这个页申请了)。
- 总页数
- 空闲页数
- 大块内存数
- 页链表头
- 页链表尾
- 从哪个页开始尝试申请空间
- 大块内存链表头
对外接口
- MemoryPoolInit 对MEMORY_POOL对象进行初始化,可以指定预分配的页数。
- MemoryPoolDestroy 对已经初始化的对象进行销毁。
- MemoryPoolMalloc 对内存池对象申请空间。
- MemoryPoolFree 对内存池对象分配的空间进行释放。
具体操作
- MemoryPoolInit 调用MemoryLargeMalloc创建一个大块内存结构体当作大块内存链表的头结点(哨兵),便于后面链表指针的统一处理。先创建一页,然后按照指定页数或者默认页数(指定页数为0),调用MemoryPageAdd进行分配页。如果函数申请malloc过程中返回NULL则调用MemoryPoolDestroy销毁内存池。
- MemoryPoolDestroy 遍历大块内存链表和页链表,并依次释放。
- MemoryPoolMalloc 如果申请空间大于等于页的大小,则调用MemoryLargeMalloc申请大块内存空间。否则调用MemoryPageMalloc申请小块内存空间。
- MemoryPoolFree 如果知道分配的内存大小,则可以知道分配的是大块内存还是小块内存,直接调用对应的处理函数即可MemoryLargeFree和MemoryPageFree。如果不知道(size为0,一般情况),先调用MemoryLargeFree再调用MemoryPageFree,效率很低。改进方法:1. 可以使用布隆过滤器来筛选出大部分不是大块内存的情况,可以直接调用MemoryPageFree,否则先调用MemoryLargeFree再调用MemoryPageFree。这种方法可以提高一定的效率。2. 可以使用红黑树来保存相关信息,以分配的地址为key,分配地址大小和分配地址对应的结构体地址为value。可以极大的提高效率。
- MemoryLargeMalloc 直接malloc出sizeof(MEMORY_LARGE)+size大小的结点,插入到链表尾部,返回data首地址。
- MemoryLargeFree 遍历每个结点找到data首地址相同的则释放并返回0,未找到则返回-1(表示不是大块内存空间)。
- MemoryPageAdd 新增指定数量的页(malloc出sizeof(MEMORY_PAGE)+页的大小的结点),依次插入到链表尾部。总数增加,空闲数量增加。
- MemoryPageDel 从页链表尾部开始删除指定数量的页。
- MemoryPageMalloc 从当前指向的页开始尝试分配空间,如果该页的未分配空间不够则(尝试数增加)再尝试下一页,所有的页都无法分配则调用MmeoryPageAdd增加指定倍率的空间(当前数量,相当于空间*2),然后再进行分配。如果是最开始的当前页分配成功,直接将尝试次数归零。如果尝试次数等于3,则将page_current指向下一页。分配成功将分配页被使用次数加一。
- MemoryPageFree 遍历每一页,查看data地址是否在当前页可使用的空间区间内。找到对应的页,将被使用次数减一。如果被使用次数等于0,则表示当前页是空页了,把该页从链表中抽出来并插入到链表尾部并将current指向data首地址,如果只有一个页就只将current指向data首地址就行。如果空闲率大于等于指定空闲率(0.75),则调用MemoryPageDel将页链表删除一半。
代码实现(链表)
memory_pool.h
/******************************************************************************
*
* Copyright (C), 2001-2005, Huawei Tech. Co., Ltd.
*
*******************************************************************************
* File Name : memory_pool.h
* Version : Initial Draft
* Author : sst
* Created : 2021/7/22
* Last Modified :
* Description : memory_pool.c header file
* Function List :
*
*
* History:
*
* 1. Date : 2021/7/22
* Author : sst
* Modification : Created file
*
******************************************************************************/
#ifndef __MEMORY_POOL_H__
#define __MEMORY_POOL_H__
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */
/*==============================================*
* include header files *
*----------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
/*==============================================*
* constants or macros define *
*----------------------------------------------*/
#define MEMORY_POOL_PAGE_SIZE 4096 // large >= it, page < it
#define MEMORY_PAGE_NUM_DEF 1 // > 0, init create page count
#define MEMORY_POOL_ADD_RATE 1 // add page count = num_page * it
#define MEMORY_POOL_DEL_POINT 0.75 // delete point > num_page_idle / num_page
#define MEMORY_POOL_DEL_RATE 0.5 // delete count = num_page * it
#ifdef INFO_FLAG
#define LARGE_FREE -1
#define PAGE_FREE -2
#define PAGE_CRETE -3
#define PAGE_FREE_APPLY -4
#endif
/*==============================================*
* project-wide global variables *
*----------------------------------------------*/
/*==============================================*
* routines' or functions' implementations *
*----------------------------------------------*/
typedef struct _MEMORY_LARGE {
struct _MEMORY_LARGE *next;
unsigned char data[0];
} MEMORY_LARGE;
typedef struct _MEMORY_PAGE {
int used;
void *current; // current
void *end; // rear + 1, address can't use
struct _MEMORY_PAGE *prev;
struct _MEMORY_PAGE *next;
unsigned char data[0];
} MEMORY_PAGE;
typedef struct _MEMORY_POOL {
#ifdef INFO_FLAG
FILE *info_fd;
#endif
int page_try;
int page_num;
int page_idle_num;
int large_num;
struct _MEMORY_PAGE *page_head;
struct _MEMORY_PAGE *page_rear;
struct _MEMORY_PAGE *page_current;
struct _MEMORY_LARGE *large_head;
} MEMORY_POOL;
#ifdef INFO_FLAG
extern void InfoClose(MEMORY_POOL *memory_pool);
extern void InfoOpen(MEMORY_POOL *memory_pool, const char *info_path);
extern void InfoWrite(MEMORY_POOL *memory_pool, void *data, int size);
#endif
extern int MemoryLargeFree(MEMORY_POOL *memory_pool, void *data);
extern void* MemoryLargeMalloc(MEMORY_POOL *memory_pool, int size);
extern int MemoryPageAdd(MEMORY_POOL *memory_pool, int count);
extern void MemoryPageDel(MEMORY_POOL *memory_pool, int count);
extern int MemoryPageFree(MEMORY_POOL *memory_pool, void *data);
extern void MemoryPageInit(MEMORY_PAGE *page);
extern void* MemoryPageMalloc(MEMORY_POOL *memory_pool, int size);
extern void MemoryPoolDestroy(MEMORY_POOL *memory_pool);
extern void MemoryPoolFree(MEMORY_POOL *memory_pool, void *data, int size);
extern MEMORY_POOL* MemoryPoolInit(int page);
extern void* MemoryPoolMalloc(MEMORY_POOL *memory_pool, int size);
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
#endif /* __MEMORY_POOL_H__ */
memory_pool.c
#include "memory_pool.h"
void* MemoryPoolMalloc(MEMORY_POOL *memory_pool, int size) {
if(size >= MEMORY_POOL_PAGE_SIZE) {
return MemoryLargeMalloc(memory_pool, size);
} else {
return MemoryPageMalloc(memory_pool, size);
}
}
void MemoryPoolFree(MEMORY_POOL *memory_pool, void *data, int size) {
if(size) { // remember memory size
if(size >= MEMORY_POOL_PAGE_SIZE) {
if(MemoryLargeFree(memory_pool, data) == -1) {
printf("free error, object doesn't in memory_pool\n");
exit(-1);
}
} else {
if(MemoryPageFree(memory_pool, data) == -1) {
printf("free error, object doesn't in memory_pool\n");
exit(-1);
}
}
} else {
if(MemoryLargeFree(memory_pool, data) == -1) {
if(MemoryPageFree(memory_pool, data) == -1) {
printf("free error, object doesn't in memory_pool\n");
exit(-1);
}
}
}
}
void* MemoryLargeMalloc(MEMORY_POOL *memory_pool, int size) {
MEMORY_LARGE *large_new =
(MEMORY_LARGE*)malloc(sizeof(MEMORY_LARGE) + size);
if(large_new == NULL) return NULL;
#ifdef INFO_FLAG
InfoWrite(memory_pool, large_new->data, size);
#endif
++memory_pool->large_num;
large_new->next = memory_pool->large_head->next;
memory_pool->large_head->next = large_new;
return (void*)large_new->data;
}
int MemoryLargeFree(MEMORY_POOL *memory_pool, void *data) {
MEMORY_LARGE *current = memory_pool->large_head, *large_delete;
while(current->next != NULL) {
if(data == current->next->data) {
large_delete = current->next;
current->next = large_delete->next;
#ifdef INFO_FLAG
InfoWrite(memory_pool, large_delete->data, LARGE_FREE);
#endif
free(large_delete);
--memory_pool->large_num;
return 0;
}
current = current->next;
}
return -1;
}
void* MemoryPageMalloc(MEMORY_POOL *memory_pool, int size) {
MEMORY_PAGE *page = memory_pool->page_current;
while(1) {
while(page) {
if(page->current + size < page->end) {
void *res = page->current;
#ifdef INFO_FLAG
InfoWrite(memory_pool, res, size);
#endif
if(page == memory_pool->page_current) {
memory_pool->page_try = 0;
}
if(!page->used) memory_pool->page_idle_num--;
++page->used;
page->current += size;
return res;
} else {
++memory_pool->page_try;
if(memory_pool->page_try == 3)
memory_pool->page_current = memory_pool->page_current->next;
}
page = page->next;
}
page = memory_pool->page_rear;
if(MemoryPageAdd(memory_pool,
memory_pool->page_num * MEMORY_POOL_ADD_RATE) == -1)
return NULL;
page = page->next;
}
return NULL;
}
int MemoryPageFree(MEMORY_POOL *memory_pool, void *data) {
MEMORY_PAGE *page = memory_pool->page_head;
while(page) {
if((unsigned long int)page->data <= (unsigned long int)data &&
(unsigned long int)data < (unsigned long int)page->end) {
#ifdef INFO_FLAG
InfoWrite(memory_pool, data, PAGE_FREE_APPLY);
#endif
--page->used;
if(!page->used) {
page->current = page->data;
if(page->prev == NULL) {
if(page->next == NULL) return 0;
memory_pool->page_head = page->next;
page->next->prev = NULL;
} else {
page->prev->next = page->next;
page->next->prev = page->prev;
}
if(memory_pool->page_current == page &&
page->next != NULL) {
memory_pool->page_current = page->next;
}
memory_pool->page_rear->next = page;
page->prev = memory_pool->page_rear;
page->next = NULL;
memory_pool->page_rear = page;
++memory_pool->page_idle_num;
while(MEMORY_POOL_DEL_POINT <=
(1.0 * memory_pool->page_idle_num / memory_pool->page_num) &&
memory_pool->page_num > MEMORY_PAGE_NUM_DEF) {
MemoryPageDel(memory_pool,
memory_pool->page_num * MEMORY_POOL_DEL_RATE);
}
}
return 0;
}
page = page->next;
}
return -1;
}
void MemoryPageInit(MEMORY_PAGE *page) {
page->current = page->data;
page->end = page->data + MEMORY_POOL_PAGE_SIZE;
page->next = NULL;
page->prev = NULL;
page->used = 0;
}
void MemoryPageDel(MEMORY_POOL *memory_pool, int count) {
MEMORY_PAGE *rear = memory_pool->page_rear, *wait_delete;
memory_pool->page_num -= count;
memory_pool->page_idle_num -= count;
int i;
for(i = 0; i < count; ++i) {
wait_delete = rear;
rear = rear->prev;
#ifdef INFO_FLAG
InfoWrite(memory_pool, wait_delete->data, PAGE_FREE);
#endif
free(wait_delete);
}
rear->next = NULL;
memory_pool->page_rear = rear;
}
int MemoryPageAdd(MEMORY_POOL *memory_pool, int count) {
if(!count) return 0;
int i;
MEMORY_PAGE *prev = memory_pool->page_rear, *rear;
for(i = 0; i < count; ++i) {
rear = (MEMORY_PAGE*)malloc(sizeof(MEMORY_PAGE) + MEMORY_POOL_PAGE_SIZE);
if(rear == NULL) goto error_add;
#ifdef INFO_FLAG
InfoWrite(memory_pool, rear->data, PAGE_CRETE);
#endif
MemoryPageInit(rear);
prev->next = rear;
rear->prev = prev;
prev = rear;
}
rear->next = NULL;
memory_pool->page_num += count;
memory_pool->page_idle_num += count;
memory_pool->page_rear = rear;
return 0;
error_add:
prev = memory_pool->page_rear->next;
memory_pool->page_rear->next = NULL;
while(prev != NULL) {
rear = prev;
prev = prev->next;
#ifdef INFO_FLAG
InfoWrite(memory_pool, rear, PAGE_FREE);
#endif
free(rear);
}
return -1;
}
void MemoryPoolDestroy(MEMORY_POOL *memory_pool) {
MEMORY_LARGE *memory_large = memory_pool->large_head, *large_delete;
while(memory_large != NULL) {
large_delete = memory_large;
memory_large = memory_large->next;
#ifdef INFO_FLAG
InfoWrite(memory_pool, large_delete, LARGE_FREE);
#endif
free(large_delete);
}
MEMORY_PAGE *memory_page = memory_pool->page_head, *page_delete;
while(memory_page != NULL) {
page_delete = memory_page;
memory_page = memory_page->next;
#ifdef INFO_FLAG
InfoWrite(memory_pool, page_delete, PAGE_FREE);
#endif
free(page_delete);
}
#ifdef INFO_FLAG
InfoClose(memory_pool);
#endif
free(memory_pool);
}
// init memory pool, page = 0 use default page count
MEMORY_POOL* MemoryPoolInit(int page) {
MEMORY_POOL *memory_pool = (MEMORY_POOL*)malloc(sizeof(MEMORY_POOL));
#ifdef INFO_FLAG
InfoOpen(memory_pool, "infomation.txt");
#endif
// as guard
memory_pool->large_head = (MEMORY_LARGE*)malloc(sizeof(MEMORY_LARGE));
if(memory_pool->large_head == NULL) goto error_init;
#ifdef INFO_FLAG
#endif
memory_pool->page_head =
(MEMORY_PAGE*)malloc(sizeof(MEMORY_PAGE) + MEMORY_POOL_PAGE_SIZE);
if(memory_pool->page_head == NULL) goto error_init;
#ifdef INFO_FLAG
InfoWrite(memory_pool, memory_pool->page_head->data, PAGE_CRETE);
#endif
MemoryPageInit(memory_pool->page_head);
memory_pool->page_rear = memory_pool->page_head;
memory_pool->page_current = memory_pool->page_head;
memory_pool->large_num = 0;
memory_pool->page_num = 1;
memory_pool->page_try = 0;
if(page == 0) page = MEMORY_PAGE_NUM_DEF;
if(MemoryPageAdd(memory_pool, page - 1) == -1)
goto error_init;
memory_pool->page_idle_num = memory_pool->page_num;
return memory_pool;
error_init:
MemoryPoolDestroy(memory_pool);
return NULL;
}
#ifdef INFO_FLAG
void InfoOpen(MEMORY_POOL *memory_pool, const char *info_path) {
memory_pool->info_fd = fopen(info_path, "w");
if(memory_pool->info_fd == NULL)
perror("fopen error : ");
}
void InfoClose(MEMORY_POOL *memory_pool) {
fclose(memory_pool->info_fd);
}
void InfoWrite(MEMORY_POOL *memory_pool, void *data, int size) {
char buffer[1024];
if(size >= MEMORY_POOL_PAGE_SIZE) {
sprintf(buffer, "create large, data : %lx, size : %d\n",
(unsigned long int)data, size);
} else if(size > 0) {
sprintf(buffer, "apply page, data : %lx, size : %d\n",
(unsigned long int)data, size);
} else if(size == 0) {
sprintf(buffer, "create large, data : %lx, size : %d\n",
(unsigned long int)data, size);
} else if(size == PAGE_CRETE) {
sprintf(buffer, "create page, data : %lx, size : %d\n",
(unsigned long int)data, size);
} else if(size == LARGE_FREE) {
sprintf(buffer, "free large, data : %lx\n", (unsigned long int)data);
} else if(size == PAGE_FREE) {
sprintf(buffer, "free page, data : %lx\n", (unsigned long int)data);
} else if(size == PAGE_FREE_APPLY) {
sprintf(buffer, "free apply, data : %lx\n", (unsigned long int)data);
}
if(fwrite(buffer, strlen(buffer), 1, memory_pool->info_fd) == 0)
perror("write error : ");
}
#endif
test_project.c
#include "lib/memory_pool.h"
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
// spend time = current time - old time
// day + hours + minute + second + millisecond + mincrosecond + info
void TimeCurrentSpend(struct timeval *time_old, char *info)
{
struct timeval now;
gettimeofday(&now, NULL);
if(now.tv_usec < time_old->tv_usec)
{
now.tv_sec--;
now.tv_usec += 1000000;
}
time_t sec = now.tv_sec - time_old->tv_sec;
char info_old[strlen(info) + 1];
strcpy(info_old, info);
sprintf(info, "%2ldd,%2ldh,%2ldm,%2lds,%4ldms,%4ldus : %s",
sec /8640, // day
(sec / 360) % 24, // hours
(sec / 60) % 60, // minute
sec % 60, // second
(now.tv_usec - time_old->tv_usec) / 1000, // millisecond
(now.tv_usec - time_old->tv_usec) % 1000, // mincrosecond
info_old); // info
}
void WorkTime(void (*work)(void *args), void *args, char *info) {
char time_buffer[1024];
struct timeval old;
gettimeofday(&old, NULL);
work(args);
sprintf(time_buffer, "%s", info);
TimeCurrentSpend(&old, time_buffer);
printf("%s", time_buffer);
}
void TestMemoryPoolLargeMallocPage(void *args) {
const int n = *(int*)args;
MEMORY_POOL *memory_pool = MemoryPoolInit(0);
int i, *data[n];
for(i = 0; i < n; ++i) {
data[i] = (int*)MemoryPoolMalloc(memory_pool, sizeof(int));
*data[i] = i;
// printf("data[%d] = %d\n", i, *data[i]);
}
for(i = 0; i < n; ++i) {
// printf("free data[%d]\n", i);
MemoryPoolFree(memory_pool, data[i], 0);
}
MemoryPoolDestroy(memory_pool);
}
void TestMemoryPoolMallocPage(void *args) {
const int n = *(int*)args;
MEMORY_POOL *memory_pool = MemoryPoolInit(0);
int i, *data;
for(i = 0; i < n; ++i) {
data = (int*)MemoryPoolMalloc(memory_pool, sizeof(int));
*data = i;
// printf("%d\n", *data);
MemoryPoolFree(memory_pool, data, sizeof(int));
}
MemoryPoolDestroy(memory_pool);
}
void TestLargeMalloc(void *args) {
const int n = *(int*)args;
int i, *data[n];
for(i = 0; i < n; ++i) {
data[i] = (int*)malloc(sizeof(int));
*data[i] = i;
// printf("data[%d] = %d\n", i, *data[i]);
}
for(i = 0; i < n; ++i) {
// printf("free data[%d]\n", i);
free(data[i]);
}
}
void TestMalloc(void *args) {
const int n = *(int*)args;
int i, *data;
for(i = 0; i < n; ++i) {
data = (int*)malloc(sizeof(int));
*data = i;
// printf("%d\n", *data);
free(data);
}
}
int main(int argc, char **argv)
{
/*
MEMORY_POOL *memory_pool = MemoryPoolInit(0);
while(1) {
int i, j, *data[1024 * 5], *large[1024];
for(i = 0; i < 1024; ++i) {
large[i] = (int*)MemoryPoolMalloc(memory_pool, sizeof(int) * 1024 * 5);
}
for(i = 0; i < 1024 * 5; ++i) {
for(j = 0; j < 1024; ++j) {
large[j][i] = i;
// printf("large[%d][%d] = %d\n", j, i, large[j][i]);
}
data[i] = (int*)MemoryPoolMalloc(memory_pool, sizeof(int));
*data[i] = i;
printf("data[%d] = %d\n", i, *data[i]);
}
for(i = 0; i < 1024 * 5; ++i) {
printf("free data[%d]\n", i);
MemoryPoolFree(memory_pool, data[i], 0);
}
for(i = 0; i < 1024; ++i) {
printf("free large[%d]\n", i);
MemoryPoolFree(memory_pool, large[i], 0);
}
char in;
in = getchar();
if(in == 'q') break;
}
MemoryPoolDestroy(memory_pool);
*/
char info[1024];
int n = 1e6;
sprintf(info, "memory pool large malloc page test\n");
WorkTime(TestMemoryPoolLargeMallocPage, (void*)&n, info);
sprintf(info, "malloc large malloc test\n");
WorkTime(TestLargeMalloc, (void*)&n, info);
sprintf(info, "memory pool malloc page test\n");
WorkTime(TestMemoryPoolMallocPage, (void*)&n, info);
sprintf(info, "malloc test\n");
WorkTime(TestMalloc, (void*)&n, info);
return 0;
}
效果测试
- 使用内存池和malloc同时开辟大量的空间,内存池效果较好(取消代码优化)
- 使用内存池和malloc开辟一个空间free一个空间,malloc效果较好。(取消代码优化)
- 保留代码优化
- 开辟大量空间时,malloc存在寻址的操作,有碎片化的话,malloc肯定会影响性能,所以内存池较快。
- 开辟一个空间释放一个空间,内存池会进行额外的操作,所以malloc较快。这还是我测试时使用了MemoryPoolFree最后一个参数,要不然还要判断是不是大块存储空间,就更慢了。
- 不过,内存池用来判断内存泄漏这是malloc办不到的,开启info写文件也会影响内存池性能,所以我设置了debug的时候define INFO_FLAG就可以开启info。
|