c++性能优化:单线程内存池

前言

本章主要讲的是单线程内存池的构建。我们都知道,内存频繁的分配和释放会导致程序性能的降低,主要的原因是内存管理上的消耗,以及有些应用程序会在使用的过程中以某种特殊的方式使用内存,导致会出现不需要内存的地方出现性能的损失。我们可以通过自己定义内存管理的方式达到管理内存的目的,防止程序对于内存的滥用。内存管理器的设计是多维的,主要考虑两个方面,第一个是内存的大小固定还是可变,第二个是单线程还是多线程。本章讨论的管理器主要是单线程方面的。

版本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);
}

以上就是内存分配可变的内存池,这样的话就可以忽略分配内存的大小,管理器内存会计算需要分配的大小,可能执行的效率会比固定大小的内存分配器慢,但是他的灵活性和功能性更强。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值