Box2D源码分析:栈内存分配B2StackAllocator

B2StackAllocator主要是为了运行一个步长时满足box2d需要的临时内存空间,作为栈分配器来防止单步堆分配。(这是官方的说明,我没弄懂是什么意思,有知道的希望能说明一下:-D

首先给出大概的图给大家一个感受,对于代码更容易理解。

它使用了数据结构stack的后进先出(LIFO)的方式。所以AllocateFree需要成对使用。


在我看来,它相对于使用SOA实现的B2BlockAllocator主要有以下区别:

a)B2StackAllocator可使用的内存大小固定,而B2BlockAllocator可以扩大内存池;

b)B2StackAllocator内分配的内存大小没有具体要求,只要不超过内存数组剩余大小即可,而B2BlockAllocator只能分配早已指定好的几种block大小类型;

c)B2StackAllocator使用栈思想,后进先出(LIFO),故其分配的空间是连续的。B2BlockAllocator使用链表形式,并利用m_freeLists在这些链表节点中指来指去,故分配的空间可以说是不连续的;

暂时只能想到这些,如有错误或者补充欢迎提出,如果有人能指出两者较为具体的使用环境就更好了。:-D

好了,知道了结构,下面的代码应该问题不大。

首先看一下头文件:

const int32 b2_stackSize = 100 * 1024;	   //内存数组最大内存100k
const int32 b2_maxStackEntries = 32;    //能存储的最多的栈实体数量

struct b2StackEntry
{
	char* data;         //指向使用的内存头位置
	int32 size;         //该内存大小
	bool usedMalloc;   //是否在堆上申请
};

// This is a stack allocator used for fast per step allocations.
// You must nest allocate/free pairs. The code will assert
// if you try to interleave multiple allocate/free pairs.
class b2StackAllocator
{
public:
	b2StackAllocator();
	~b2StackAllocator();

	void* Allocate(int32 size);
	void Free(void* p);

	int32 GetMaxAllocation() const;    //获取历史总分配内存大小(不扣释放掉的)

private:
    //内存数组,看成内存池呗
	char m_data[b2_stackSize];
    //内存池中已使用的内存大小
    //可用来获取内存池未使用的内存头位置
	int32 m_index;
    //栈实体数组m_entries中所有实体的内存总大小
    //与上面m_index不同的是
    //m_allocation包括了在堆上分配的内存大小
	int32 m_allocation;
    //历史上总分配内存大小
	int32 m_maxAllocation;
    //栈实体数组
	b2StackEntry m_entries[b2_maxStackEntries];
    //栈实体数组中实体数量
	int32 m_entryCount;
};

说明已在注释中,下面介绍实现文件。

构造函数

b2StackAllocator::b2StackAllocator()
{
	m_index = 0;
	m_allocation = 0;
	m_maxAllocation = 0;
	m_entryCount = 0;
}

初始化内存池中已使用的内存大小m_index = 0,总分配内存大小(包括堆中)m_allocation = 0,历史总分配内存大小m_maxAllocation = 0,栈实体数量m_entryCount = 0.

析构函数

b2StackAllocator::~b2StackAllocator()
{
	b2Assert(m_index == 0);
	b2Assert(m_entryCount == 0);
}

只需要确保内存池已使用的内存大小以及栈实体数量为0。即验证内存是否完全释放和元素是否完全出栈。故Allocate与free务必成对使用。

内存分配

void* b2StackAllocator::Allocate(int32 size)
{
    //确保栈实体数组还未使用完毕
	b2Assert(m_entryCount < b2_maxStackEntries);
    //获取实体数组中未使用的头位置
    //不过,既然是数组
    //为什么不直接使用b2StackEntry entry = m_entries[m_entryCount]呢?
	b2StackEntry* entry = m_entries + m_entryCount;
	entry->size = size;
	if (m_index + size > b2_stackSize)
	{
        //若申请的大小超过剩余可分配数
        //则在堆上申请内存
        //同时设usedMalloc = true
		entry->data = (char*)b2Alloc(size);
		entry->usedMalloc = true;
	}
	else
	{
        //若申请的大小未超过剩余可分配数
        //直接在内存池中申请内存
        //获取内存池中未使用的内存首地址
		entry->data = m_data + m_index;
        //设usedMalloc = false
		entry->usedMalloc = false;
        //增加内存池中内存使用量
		m_index += size;
	}
    //增加内存使用量(包括堆中)
	m_allocation += size;
    //设置历史最大分配内存
	m_maxAllocation = b2Max(m_maxAllocation, m_allocation);
    //增加栈实体使用数
	++m_entryCount;

	return entry->data;
}

该部分代码也是简洁易懂,只需注意申请的内存是否超过内存池剩余可用内存。

释放函数

void b2StackAllocator::Free(void* p)
{
    //要释放自然要有可以释放的元素啦
    //确保有元素可以释放
	b2Assert(m_entryCount > 0);
    //获取最后一个实体,即准备要POP栈顶部啦
	b2StackEntry* entry = m_entries + m_entryCount - 1;
    //保证释放的内存确实是参数内存
    //这里再次提醒,一个Allocate务必对应一个Free
    //否则会造成这里assert
	b2Assert(p == entry->data);
	if (entry->usedMalloc)
	{
        //在堆中申请的
        //释放堆中内存
		b2Free(p);
	}
	else
	{
        //在内存池中申请的
        //减少已使用的内存
        //相当于设置其为可以被使用
        //下次有新申请直接把它送出去,任由其被覆盖
		m_index -= entry->size;
	}
    //减少总分配内存量
	m_allocation -= entry->size;
    //减少实体数组中已使用的实体量
	--m_entryCount;
    //注意设置p为null
	p = NULL;
}

值得注意的只有设置pNULL吧,其他部分在代码注释中已经说明。

获取历史最大内存分配量

int32 b2StackAllocator::GetMaxAllocation() const
{
    //直接返回成员变量m_maxAllocation即可
	return m_maxAllocation;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值