您的位置:首页 > 其它

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

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

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

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



在我看来,它相对于使用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;
}


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

获取历史最大内存分配量

int32 b2StackAllocator::GetMaxAllocation() const
{
//直接返回成员变量m_maxAllocation即可
return m_maxAllocation;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: