前言
本章主要讲的是单线程内存池的构建。我们都知道,内存频繁的分配和释放会导致程序性能的降低,主要的原因是内存管理上的消耗,以及有些应用程序会在使用的过程中以某种特殊的方式使用内存,导致会出现不需要内存的地方出现性能的损失。我们可以通过自己定义内存管理的方式达到管理内存的目的,防止程序对于内存的滥用。内存管理器的设计是多维的,主要考虑两个方面,第一个是内存的大小固定还是可变,第二个是单线程还是多线程。本章讨论的管理器主要是单线程方面的。
版本0 new()和delete()
从原理上说,默认的内存管理器是通用的,最终的内存都是通过new和delete两个函数实现的。因此在正常的调用中,new和delete这两个函数的设计都会比较复杂,为了应对多线程的场景,而我们只需要应对但线程的场景,因此设计可以简单一些。
这里我们举出书中的例子,有理数的对象需要频繁的分配和释放内存:
class Rational {
public:
Ratinal(int a = 1, int b = 1)
: n(a)
, d(b)
{}
private:
int n;
int d;
};
// test main
int main()
{
Rational *array[1000];
for (int j = 0; j < 500; j++) {
for (int i = 0; i < 1000; i++) {
array[i] = new Rational(i);
}
for (int i = 0; i < 1000; i++) {
delete array[i];
}
}
}
上面的代码我想应该很好理解,书中说是这段代码的耗时是1500ms,可想而知还是很消耗性能的。
专用Rational内存管理器
为了避免上述代码中的频繁的内存分配,我们预先创建一个Ratinal对象的静态链表,需要的时候取出一个,操作完成之后放到空闲的队列等待下次分配。
class NextFreeList {
public:
NextFreeList *next;
};
class Rational {
...
static NextFreeList* freeList;
};
这里的NextFreeList就是空闲的列表,里面存放的是每个Rational的地址。同时还需要重载new和delete。
class Rational {
public:
Ratinal(int a = 1, int b = 1)
: n(a)
, d(b)
{}
inline void* operator new (size_t size);
inline void operator delete (void *doomed, size_t size);
static void newMemPool() {expandTheFreeList();}
static void deleteMemPool();
private:
static NextFreeList *freeList;
static void expandTheFreeList();
enum {EXPAND_SIZE = 32};
int n;
int d;
};
操作符new从空闲列表中分配一个Rational对象。如果空闲列表为空,那么将得到扩展。先去掉空闲列表的头部,再调整空闲列表的指针后再返回。
inline void* Rational::operator new ()
{
if (freeList == 0) {
expandTheFreeList();
}
NextFreeList* head = freeList;
freeList = head->next;
return head;
}
inline void Rational::operator delete(void *doomed, size_t size)
{
NextFreeList *head = static_cast<NextFreeList*>(doomed);
head->next = freeList;
freeList = head;
}
void Rational::expandTheFreeList()
{
size_t size = (sizeof(Rational) > sizeof(NextFreeList*)) ? sizeof(Rational) : sizeof(NextFreeList*);
NextFreeList* runner = static_cast<NextFreeList*>(new char[size]);
freeList = runner;
for (int i = 0; i < EXPAND_SIZE; i++) {
runner->next = static_cast<NextFreeList*>(new char(size));
runner = runner->next;
}
runner->next = 0;
}
void Rational::deleteMemPool()
{
NextFreeList* nextPtr;
for (nextPtr = freeList; nextPtr != NULL; nextPtr = freeList) {
freeList = freeList->next;
delete[] nextPtr;
}
}
通过上述的第一版的改正,可以在不用直接分配内存的情况下直接获得内存,因此耗时直接从1500ms降低到了43ms。
版本二:固定大小对象的内存池
这一节主要是将上面使用的内存池使用模板封装,这样的话就可以实现不同对象的内存分配。
见代码:
template<class T>
class MemPool {
public:
MemoryPool(size_t size = EXPAND_SIZE);
~MemPool();
inline void* alloc(size_t size);
inline void free(void* element);
private:
MemoryPool<T> * next;
enum {EXPAND_SIZE = 32};
void expandTheFreeList(int howMany = EXPAND_SIZE);
};
template<class T>
MemoryPool<T>::MemoryPool(size_t size)
{
expandTheFreeList(size);
}
template<class T>
MemoryPool<T>::~MemoryPool()
{
Memory<T> *nextPtr = next;
for (nextPtr = next; nextPtr != NULL; nextPtr = next) {
next = next->next;
delete[]nextPtr;
}
}
template<class T>
void * MemoryPool<T>::alloc(size_t size)
{
if (!next) {
expandTheFreeList(size);
}
MemoryPool* head = next;
next = head->next;
return head;
}
template<class T>
void MemoryPool<T>::free()
{
MemoryPool* head = static_cast<MemoryPool<T>*>(doomed);
head->next = next;
next = head;
}
template<class T>
void MemoryPool<T>::expandTheFreeList(int howMany)
{
size_t size = (sizeof(T) > sizeof(MemPool<T>*)) ? sizeof(T) : sizeof(MemPool<T>*);
NextFreeList* runner = static_cast<MemPool<T>*>(new char[size]);
next = runner;
for (int i = 0; i < howMany; i++) {
runner->next = static_cast<MemPool<T>*>(new char(size));
runner = runner->next;
}
runner->next = 0;
}
以上内存池的出现,Rational类就不需要自己管理内存的分配,直接交给MemPool类。
class Rational {
public:
Ratinal(int a = 1, int b = 1)
: n(a)
, d(b)
{}
inline void* operator new (size_t size);
inline void operator delete (void *doomed, size_t size);
static void newMemPool() {memPool = new MemoryPool<Rational>;}
static void deleteMemPool();
private:
static MemoryPool<Rational>* memPool;
int n;
int d;
};
至此,我们单线程的内存池的分配就大概这些,下面会接触内存大小可变的情况
版本3:单线程内存大小可变的内存池
可变大小的内存管理器也是非常常用的一种case。比如说事先不知道需要分配内存的大小,这时就需要有可变大小的内存分配器来满足这样的需求。
假定这个类是MemoryChunk,用于将各个内存块串成一个块序列。实际MemoryChunk是版本一种的NextFreeList的更为干净的版本,将内存串联的next指针和内存指向的mem指针分开,这样的话就不需要进行类型转换。
class MemoryChunk
{
public:
MemoryChunk(MemoryChunk* next, size_t size);
~MemoryChunk();
void * malloc(size_t size);
void free(void* doomed);
MemoryChunk* nextMemoryChunk(){return next;}
size_t spaceAvailable(){return chunkSize - sizeAlreadyAlloced};
enum {
DEFAULE_CHUNK_SIZE = 4096
};
private:
size_t chunkSize;
size_t sizeAlreadyAlloced;
MemoryChunk* next;
void *mem;
};
下面的函数的具体实现,构造函数相对简单,初始化内存用:
MemoryChunk::MemoryChunk(MemoryChunk* nextChunk, size_t reqSize)
{
chunkSize = reqSize > DEFAULE_CHUNK_DIZE ? reqSize : DEFAULE_CHUNK_DIZE;
next = nextChunk;
sizeAlreadyAlloced = 0;
mem = new char[chunkSize];
}
// 析构函数的实现就是释放自己申请的内存块
MemoryChunk::~MemoryChunk()
{
delete[] mem;
}
接下来是申请和释放内存的函数:
void* MemoryChunk::alloc(size_t size)
{
void* addr = static_cast<void*>(static_cast<size_t>(mem) + sizeAlreadyAlloc);
sizeAlreadyAlloc += size;
return addr;
}
// 内存的释放实际在析构函数时会做,所以可以不做什么操作
inline void MemoryChunk::free(void* doomed)
{}
下面就是使用这个辅助类的内存管理器类:
class ByteMemoryPool {
public:
ByteMemoryPool(size_t size = DEFAULT_CHUNK_SIZE);
~ByteMemoryPool();
inline void* malloc(size_t);
inline void free(void* doomed);
enum {
DEFAULE_CHUNK_SIZE = 4096
};
private:
MemoryChunk* listOfMemory;
void expandSpace(size_t reqSize);
};
尽管内存列表中可以有不止一块的内存,但是始终只有第一块是可以用于分配的内存,其他的都是已经分配使用的内存。
ByteMemoryPool::ByteMemoryPool(size_t size)
{
expandSpace(size);
}
ByteMemoryPool::~ByteMemoryPool()
{
MemoryChunk* mem = listOfMemory;
while (listOfMemory) {
listOfMemory = mem->nextMemoryChunk();
delete mem;
mem = listOfMemory;
}
}
inline void* ByteMemoryPool::alloc(size_t size)
{
size_t avaSize = listOfMemory->spaceAvailable();
if (avaSize < size) {
expandSpace(size);
}
return listOfMemory->alloc(size);
}
inline void ByteMemoryPool::free(void* doomed)
{
listOfMemory->free(doomed);
}
void ByteMemoryPool::expandSpace(size_t size)
{
listOdMemory = new MemoryChunk(size);
}
以上就是内存分配可变的内存池,这样的话就可以忽略分配内存的大小,管理器内存会计算需要分配的大小,可能执行的效率会比固定大小的内存分配器慢,但是他的灵活性和功能性更强。