游戏内存管理二之PoolAllocator(池分配器)

在上个博客:游戏内存管理一之StackAllocator(堆栈分配器)中介绍了堆内存分配的弊端和堆栈分配器(StackAllocator)的实现,这篇介绍PoolAllocator(池分配器)的实现。

 

PoolAllocator(池分配器)

在游戏引擎编程中, 常会分配大量同等尺寸的小块内存。比如我们需要分配N个矩阵对象,N个角色对象,N个武器对象,N个可绘制Mesh对象等等。这些就是池分配器。

池分配器工作方式:预先分配一大片内存,内存刚好是某种对象的N倍。比如一个Person对象是20个字节,那么我们Pool<Person>的“角色池”分配的内存就是20字节的倍数.池分配器通常采用一个存放自由元素的链表,预先分配存在N个节点的链表。当分配对象时,从中取出一个(头指针指向下一个节点);当回收对象时,池分配器的头指针指向回收的对象,这样就实现了“对象池”的分配和回收。如下图所示:

 

池分配器(PoolAllocator)的节点

template<typename T>
class PoolChunk
{
public:
	T value;
	PoolChunk<T>* nextChunk;

public:
	PoolChunk()
	{
		nextChunk = nullptr;
	}

	~PoolChunk()
	{

	}
};

 

池分配器(PoolAllocator)的链表结构

在池分配器(PoolAllocator)的构造函数中进行了池内对象的预选分配,并以链表(list)节点连接的方式存储各个池对象

template<typename T>
class PoolAllocator
{
private:
	static const size_t POOL_DEFAULT_SIZE = 200; //默认数量大小
	static const size_t DYNAMIC_INCRETA_SIZE = 50; //动态数量扩容大小, 当分配新的数量大于size时,size = size + DYNAMIC_INCRETA_SIZE
	PoolChunk<T>* dataPtr;
	PoolChunk<T>* headPtr;
	size_t size;
	size_t hasAllocateSize;

public:
	PoolAllocator(size_t inPoolSize = POOL_DEFAULT_SIZE):
		size(inPoolSize),
		hasAllocateSize(0)
	{
		dataPtr = new PoolChunk<T>[size];
		headPtr = dataPtr;

		for (size_t index = 0; index < size; ++index)
		{
			dataPtr[index].nextChunk = std::addressof(dataPtr[index + 1]);
		}
	}
}

 

分配对象Allocate

比较注意的是,当池分配器分配对象时,链表内的节点取完了,下面池分配器会进行定量的扩容,这点倒是和std::vector的capacity机制类似

	template<typename... Arguments>
	T*  (Arguments...args)
	{
		// 已经使用完池内的对象,扩大池的容量
		if (nullptr == headPtr && hasAllocateSize >= size)
		{
			PoolChunk<T>* addDataPtr = new PoolChunk<T>[DYNAMIC_INCRETA_SIZE];
			headPtr = addDataPtr;
			for (size_t index = 0; index < DYNAMIC_INCRETA_SIZE - 1; ++index)
			{
				addDataPtr[index].nextChunk = std::addressof(addDataPtr[index + 1]);
			}
		}

		T* newObject = new(std::addressof(headPtr->value)) T(std::forward<Arguments>(args)...);
		headPtr = headPtr->nextChunk;
		++hasAllocateSize;

		return newObject;
	}

 

回收对象Deallocate

	void Deallocate(T* object)
	{
		--hasAllocateSize;
		object->~T();

		PoolChunk<T>* poolChunk = reinterpret_cast<PoolChunk<T>*>(object);
		poolChunk->nextChunk = headPtr;
		headPtr = poolChunk;
	}


代码示例


class Object
{
private:
	int value;
	string uid;

public:
	Object() = default;

	Object(int inValue, string inUid):
		value(inValue),
		uid(inUid)
	{

	}

	~Object()
	{
		printf("destruct");
	}

	void PrintInfo()
	{
		printf("value = %d, uid = %s\n", value, uid.c_str());
	}
};


int main()
{
	PoolAllocator<Object> objectPool;
	Object* object1 = objectPool.Allocate(10, "object1");
	Object* object2 = objectPool.Allocate(-223, "object2");
	object1->PrintInfo();
	object2->PrintInfo();
	objectPool.Deallocate(object2);
	objectPool.Deallocate(object1);

	while (true)
	{
		object1 = objectPool.Allocate(10, "object1");
		object2 = objectPool.Allocate(-223, "object2");
		object1->PrintInfo();
		object2->PrintInfo();
		objectPool.Deallocate(object2);
		objectPool.Deallocate(object1);
	}

	system("pause");
	return 0;
}

 

参考资料

[1].《游戏引擎架构》5.2 内存管理

[2].Game Engine Tutorial [002] - Pool Allocator Theorie

[3].Game Engine Tutorial [003] - Pool Allocator Praxis

 

资源链接

https://download.csdn.net/download/qq_29523119/11860772

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值