内存池技术用来解决内存管理的问题。一般的 C++ new/delete 容易造成的问题是
- 反复申请释放,引起性能下降
- 碎片化
碎片化是什么问题?
操作系统重空闲内存不连续,此时如果申请一片大内存,导致失败,这种情况叫做外部碎片化。关键词,内存不连续
还有一种是内部碎片化,比方说,有一个申请是要33字节的内存,但是内存分配机制,每次单位给出是 64字节,有31字节是浪费的。
以上的问题使得内存池技术的要求很明显。
内存需要解决频繁申请和碎片化两大问题。
频繁申请可以通过“池化”解决。
池化是一种很常见的计算机算法。
举例而言,如果从A地到B运送货物,A地持续生产货物,B地派往A地的货车可以拉5吨货,但是到达A地时那里有1吨货。
是等装够一车货再走,还是立刻运走这1吨的货呢?取决于货物生产的效率,和AB两地往返的运输成本。通常,只要AB比较远,运费较贵,一般的选择是装满再走。
这种积累再处理的思路就是所谓的池化。
抽象看,如果一个操作可以分成多个阶段,其中某个阶段有成本,而批量操作和合并某个阶段,那么这个批量操作可以通过“池化”的方法进行提效
内存申请和释放就是具有这个特质。
实现内存池的几个关键点
- 如何把内存还回内存池,这个一般方法是需要一个标记。但是内存池一般的办法是用单向链表实现这个功能
- 解决碎片化的问题,当然就是把内存成片管理在一个地方,因此,内存预申请通过malloc得到一片连续的地址
内存池的成片内存的地址是通过一个单向链表管理的,一开始预申请 n 个块,每个块的大小固定,这些块的地址用链表串起来,然后存储链表的头地址
现在需要申请一3个块
于是从内存池的头开始数3个,将头地址偏移3个位置,代表,当前没有被分配的地址是从新的头地址开始,然后将旧的头地址返回,当然要做一些类型的转化
释放内存。当client归还内存时,要怎么做,当然不要直接delete或者free掉,这样的话,内存池不能重复利用,白做。
单向链表的使用主要目的也是在于归还,指针 ptr 是待释放归还内存池的地址,它指向的地址在内存池中时标记已使用的——从头指针顺下去没有这个 ptr——释放的时候将ptr接到,也就是插入单向链表的头结点的下一个空就行了,也可以直接插到头部。
#include <iostream>
#include <cstdlib> // std::malloc, std::free
class MemoryPool {
private:
// 内存块结构,类似于一个单向链表节点
struct Block {
Block* next;
};
Block* freeBlocks; // 指向可用内存块的链表头
std::size_t blockSize; // 每个内存块的大小
std::size_t poolSize; // 内存池中内存块的总数量
char* poolMemory; // 实际的内存池
public:
// 构造函数,创建内存池
MemoryPool(std::size_t blockSize, std::size_t poolSize)
: blockSize(blockSize), poolSize(poolSize) {
poolMemory = static_cast<char*>(std::malloc(blockSize * poolSize));
freeBlocks = nullptr;
// 初始化内存池,将所有内存块链接成一个单向链表
for (std::size_t i = 0; i < poolSize; ++i) {
Block* block = reinterpret_cast<Block*>(poolMemory + i * blockSize);
block->next = freeBlocks;
freeBlocks = block; //将每个块的地址串成一个单向链表
}
}
// 析构函数,释放内存池
~MemoryPool() {
std::free(poolMemory);
}
// 从内存池中分配一个内存块
void* allocate() {
if (!freeBlocks) {
throw std::bad_alloc(); // 如果没有可用内存块,抛出异常
}
Block* block = freeBlocks;
freeBlocks = freeBlocks->next; // 移动链表头
return block;
}
// 将内存块释放回内存池
void deallocate(void* ptr) {
Block* block = static_cast<Block*>(ptr);
block->next = freeBlocks; // 将释放的内存块加回链表
freeBlocks = block;
}
};
int main() {
// 创建内存池,每个块的大小为 32 字节,内存池有 10 个块
MemoryPool pool(32, 10);
// 从内存池分配内存
void* ptr1 = pool.allocate();
void* ptr2 = pool.allocate();
std::cout << "Allocated memory addresses: " << ptr1 << ", " << ptr2 << std::endl;
// 释放内存块回内存池
pool.deallocate(ptr1);
pool.deallocate(ptr2);
return 0;
}
上面的代码是针对一个大小为 blockSize的内存块编写的内存池
对于一般的类型,可以使用泛型