概要
一个简单内存池的实现。
内存池是一种内存分配方式。内存池主要由内存单元、内存块和内存池三部分组成。其中,内存单元是实际上使用的内存;内存块由内存块对象和多个大小相等的内存单元组成;内存池是在真正使用内存之前,先申请分配一定数量的、大小相等的内存块留作备用。
内存池的优点:
①充分利用内存空间,不会造成太多的资源浪费。
②尽量避免了内存碎片,使得内存分配效率得到提升。
③提高了程序局部性,提升了程序性能。
④比较容易控制页边界对齐和内存字节对齐,没有内存碎片的问题。
内存池的缺点:
①内存池管理(包括分配和回收)困难,效率低。
②容易造成内存空间的浪费。
整体架构流程
<MemoryPool.h>
#pragma once
#include<list>
#include<iostream>
#include <Windows.h>
#define MEMORY_BLOCK_SIZE 1024
#define MEMORY_BLOCK_COUNT 10
class UMemoryPool
{
public:
UMemoryPool(size_t blockSize, size_t blockCount)
:blockSize(blockSize),
blockCount(blockCount),
freeCount(blockCount)
{
// 分配内存池的内存
pool = new char[blockSize* blockCount];
ptr = pool;
//初始化空闲列表
for (size_t i = 0; i < blockCount; i++)
{
freeList.push_back(ptr);
ptr += blockSize;
}
}
~UMemoryPool()
{
delete []pool;
freeList.clear();
}
// 分配内存块
void* allocate();
//释放内存块
void deallocate(void* ptr);
//获取内存块剩余数量
size_t getFreeBlockCount();
private:
size_t blockSize; // 每个内存块的大小
size_t blockCount; // 内存块的个数
size_t freeCount; // 剩余空闲内存块
char* ptr;
char* pool; // 内存池的内存起始地址
std::list<char*> freeList;; // 空闲列表,存储可用内存块的索引
};
<MemoryPool.cpp>
#include "UMemoryPool.h"
void* UMemoryPool::allocate()
{
if (freeList.empty()) {
throw std::bad_alloc(); // 内存池已满
}
char* block = freeList.front();
freeList.pop_front();
freeCount--;
char* alignedBlock = reinterpret_cast<char*>((reinterpret_cast<uintptr_t>(block) + sizeof(std::max_align_t) - 1) & ~(sizeof(std::max_align_t) - 1));//偏移量和按位取反进行按位与运算
return alignedBlock;
}
void UMemoryPool::deallocate(void *ptr) {
//无效指针
if (!ptr)
{
return;
}
char* charPtr = static_cast<char*>(ptr);
auto it = freeList.begin();
freeList.push_back(charPtr);
freeCount++;
}
size_t UMemoryPool::getFreeBlockCount()
{
return freeCount;
}
技术细节
size_t blockSize; // 每个内存块的大小
size_t blockCount; // 内存块的个数
size_t freeCount; // 剩余空闲内存块
char* ptr;
char* pool; // 内存池的内存起始地址
std::list<char*> freeList;; // 空闲列表,存储可用内存块的索引
1、内存池搭建第一步,先申请一块大内存,我们称之为页(page),页里由众多内存块(block)组成。
通过构造函数,我们确定内存池的大小,并声明一个链表freeList对内存块进行管理。
UMemoryPool(size_t blockSize, size_t blockCount)
:blockSize(blockSize),
blockCount(blockCount),
freeCount(blockCount)
{
// 分配内存池的内存
pool = new char[blockSize* blockCount];
ptr = pool;
//初始化空闲列表
for (size_t i = 0; i < blockCount; i++)
{
freeList.push_back(ptr);
ptr += blockSize;
}
}
2、申请内存接口实现
检查freelist是否为空,为空就代表没有空闲内存块,获取第一块空闲内存块,字节对齐后,返回给用户,并把这块内存地址从freelist中弹出。
字节对齐的作用:
①减少对内存的读取次数,提高读取效率。
②减少内存空间的浪费,节省存储空间。
③保证数据在内存中的连续存储,减少内存碎片的产生。
④保证数据在不同的计算机上都可以正确地读取和处理,增加数据的可移植性。
void* UMemoryPool::allocate()
{
if (freeList.empty()) {
throw std::bad_alloc(); // 内存池已满
}
char* block = freeList.front();
freeList.pop_front();
freeCount--;
char* alignedBlock = reinterpret_cast<char*>((reinterpret_cast<uintptr_t>(block) + sizeof(std::max_align_t) - 1) & ~(sizeof(std::max_align_t) - 1));//偏移量和按位取反进行按位与运算
return alignedBlock;
}
3、释放内存接口实现:
释放内存,就是表示这块内存已经不再使用,进入空闲状态。将这块内存地址重新加入freelist中,待下回再次使用。
void UMemoryPool::deallocate(void *ptr) {
//无效指针
if (!ptr)
{
return;
}
char* charPtr = static_cast<char*>(ptr);
freeList.push_back(charPtr);
freeCount++;
}
4、内存池的使用
#include "UMemoryPool.h"
void main()
{
UMemoryPool pool(MEMORY_BLOCK_SIZE, MEMORY_BLOCK_COUNT); // 创建一个内存池,每个块大小为1024字节,总共10个块
for (size_t i = 0; i < 100; i++)
{
void* ptr1 = pool.allocate();
void* ptr2 = pool.allocate();
std::cout << "Allocate after: " << pool.getFreeBlockCount() << std::endl;
// 使用ptr1和ptr2...
pool.deallocate(ptr1);
pool.deallocate(ptr2);
std::cout << "deallocate After: " << pool.getFreeBlockCount() << std::endl;
Sleep(10);
}
}
小结
这是一个简单的内存池实现,还会有内存块大小调整,碎片整理,多线程环境使用等等功能,在后续优化中慢慢增加。