本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8258166
SOA,全称small object allocator,中文意思是小对象分配器。box2d虽然是用c++写的,但是并没有使用c++自带的new/delete实现内存管理,而是使用在c的malloc/free做法的基础上封装了类b2BlockAllocator进行内存管理,使得分配和使内存变得更加高效、快速。其中b2BlockAllocator就是一个SOA,下面我们就对源码进行分析。
一、b2BlockAllocator类的头文件
首先我们对头文件b2BlockAllocator.h进行大致的了解一遍。不多说,上代码:
//一次分配内存大小
const int32 b2_chunkSize = 16 * 1024;
//块子节点大小的最大值
const int32 b2_maxBlockSize = 640;
//可以申请块子节点大小的类型总数
const int32 b2_blockSizes = 14;
//块空间增量
const int32 b2_chunkArrayIncrement = 128;
//块子节点结构体[链表实现]声明
struct b2Block;
//块结构体声明
struct b2Chunk;
//这是一个小型的对象分配器,用于一次分配多个小对象
class b2BlockAllocator
{
public:
b2BlockAllocator();
~b2BlockAllocator();
//分配内存,当size>b2_maxBlockSize则直接用b2Alloc分配
void* Allocate(int32 size);
//释放内存,当size>b2_maxBlockSize则直接用b2Free释放
void Free(void* p, int32 size);
//清空内存
void Clear();
private:
//当前块的头指针
b2Chunk* m_chunks;
//当前已使用的块空间节点总数
int32 m_chunkCount;
//当前已申请的块空间节点总数
int32 m_chunkSpace;
//未被使用的内存块链表类型数组,保存了其不同类型链表的头指针
b2Block* m_freeLists[b2_blockSizes];
//申请的块大小类型数组
static int32 s_blockSizes[b2_blockSizes];
//根据要申请块的大小获取其类型索引的数组
static uint8 s_blockSizeLookup[b2_maxBlockSize + 1];
//是否已初始化s_blockSizeLookup数组,标志变量
static bool s_blockSizeLookupInitialized;
};
二、b2BlockAllocator的.c文件
下面我们看该类的具体实现,看b2BlockAllocator.c文件,
1、变量的定义
映入我们眼帘的是一些变量或结构的定义,如下代码:
int32 b2BlockAllocator::s_blockSizes[b2_blockSizes] =
{
16, // 0
32, // 1
64, // 2
96, // 3
128, // 4
160, // 5
192, // 6
224, // 7
256, // 8
320, // 9
384, // 10
448, // 11
512, // 12
640, // 13
};
uint8 b2BlockAllocator::s_blockSizeLookup[b2_maxBlockSize + 1];
bool b2BlockAllocator::s_blockSizeLookupInitialized;
struct b2Chunk
{
int32 blockSize;
b2Block* blocks;
};
struct b2Block
{
b2Block* next;
};
b2BlockAllocator::b2BlockAllocator()
{
b2Assert(b2_blockSizes < UCHAR_MAX);
m_chunkSpace = b2_chunkArrayIncrement;
m_chunkCount = 0;
m_chunks = (b2Chunk*)b2Alloc(m_chunkSpace * sizeof(b2Chunk));
memset(m_chunks, 0, m_chunkSpace * sizeof(b2Chunk));
memset(m_freeLists, 0, sizeof(m_freeLists));
if (s_blockSizeLookupInitialized == false)
{
int32 j = 0;
for (int32 i = 1; i <= b2_maxBlockSize; ++i)
{
b2Assert(j < b2_blockSizes);
if (i <= s_blockSizes[j])
{
s_blockSizeLookup[i] = (uint8)j;
}
else
{
++j;
s_blockSizeLookup[i] = (uint8)j;
}
}
s_blockSizeLookupInitialized = true;
}
}
b2BlockAllocator::~b2BlockAllocator()
{
for (int32 i = 0; i < m_chunkCount; ++i)
{
b2Free(m_chunks[i].blocks);
}
b2Free(m_chunks);
}
在构造函数b2BlockAllocator()中我们初始化相关变量,例如一开始我们就判断b2_blockSizes的有效性,接着为m_chunkSpace、m_chunkCount、m_chuns、m_freeLists、和s_blockSizeLookup的初始化。我们主要说说s_blockSizeLookup的初始化是怎样完成的。if(i > s_blockSizes[j])
{
++j;
}
s_blockSizeLookup[i] = (uint8)j;
虽然效率差了点:-D,但这样更易于理解。void* b2BlockAllocator::Allocate(int32 size)
{
if (size == 0)
return NULL;
//验证size的有效性
b2Assert(0 < size);
//申请的空间大于规定的最大值,
//直接申请,不放到块的链表中去【即m_chunks】
if (size > b2_maxBlockSize)
{
return b2Alloc(size);
}
//根据要申请的内存大小获取内存类型索引值,并判断有效性
int32 index = s_blockSizeLookup[size];
b2Assert(0 <= index && index < b2_blockSizes);
//查看是否有同类型的未被使用的内存块
if (m_freeLists[index])
{
b2Block* block = m_freeLists[index];
m_freeLists[index] = block->next;
return block;
}
else
{
//已使用的大小与已申请的块大小相等
//重新申请空间
if (m_chunkCount == m_chunkSpace)
{
//获取原来的块头,并保存到oldChunks中
b2Chunk* oldChunks = m_chunks;
//扩充块空间的大小
m_chunkSpace += b2_chunkArrayIncrement;
//申请空间,并重新赋值给m_chunks变量
m_chunks = (b2Chunk*)b2Alloc(m_chunkSpace * sizeof(b2Chunk));
//拷贝内存到m_chunks中
memcpy(m_chunks, oldChunks, m_chunkCount * sizeof(b2Chunk));
//将最新申请的内存的最后b2_chunkArrayIncrement置,防止程序中读取脏数据
//个人感觉如下写法更易于理解,只是效率要慢一点点啦
///memset(m_chunks , 0,m_chunkSpace * sizeof(b2Chunk));
///memcpy(m_chunks, oldChunks, m_chunkCount * sizeof(b2Chunk));
memset(m_chunks + m_chunkCount, 0, b2_chunkArrayIncrement * sizeof(b2Chunk));
//释放原来的块空间
b2Free(oldChunks);
}
//获取已使用块空间的尾指针
b2Chunk* chunk = m_chunks + m_chunkCount;
//申请n个块子节点内存
//并将地址赋值给块头指针
//这样的好处是不需要频繁的去内存中申请空间,不必每个节点都去申请,提高了效率
chunk->blocks = (b2Block*)b2Alloc(b2_chunkSize);
//用于调试,正式版本中将关闭_DEBUG宏,故不存在相关代码,以后我们遇到相关代码块也将忽略
#if defined(_DEBUG)
memset(chunk->blocks, 0xcd, b2_chunkSize);
#endif
//获取根据索引块大小,并赋值给块的大小
int32 blockSize = s_blockSizes[index];
chunk->blockSize = blockSize;
//获取子节点个数
//并防止转换时出现错误【个人猜测,有待考证】
int32 blockCount = b2_chunkSize / blockSize;
b2Assert(blockCount * blockSize <= b2_chunkSize);
//将子节点用链表的方式串起来
//有人不禁疑惑,这不是刚刚申请的一个连续的内存块吗?
//用头指针可以直接访问呀?干嘛要串起来?
//好处:
//可以自由的操作每个子节点,例如、释放、访问、其它方式链接等,在后面我们将看见
for (int32 i = 0; i < blockCount - 1; ++i)
{
b2Block* block = (b2Block*)((int8*)chunk->blocks + blockSize * i);
b2Block* next = (b2Block*)((int8*)chunk->blocks + blockSize * (i + 1));
block->next = next;
}
//获取最后一个内存块的子节点
//并将子节点的下一个节点置空
b2Block* last = (b2Block*)((int8*)chunk->blocks + blockSize * (blockCount - 1));
last->next = NULL;
//将申请且未使用的指针保存到m_freeLists对应类型的数组中
m_freeLists[index] = chunk->blocks->next;
//当前已使用的块空间节点总数
++m_chunkCount;
//返回块的头指针
return chunk->blocks;
}
代码的解释如上述,在此就不啰嗦了,不过我们总体分析一下,它的逻辑是:
a)、将内存按大小分为16,32,64,96,128,160,192,224,256...640等b2_blockSizes【即14】类,并按照顺序保存到数组s_blockSizes中。
b)、通过申请size的大小,判断是否大于b2_maxBlockSize,如果大于则直接分配。
c)、否则通过size,传递给s_blockSizeLookup数组找到所需要申请的类型index,将index的值在传递给链表数组m_freelists[index],查找是否有子节点,有则直接返回子节点。
d)、否则将判断m_chunks指向的动态数组是否已用完,若用完则扩充块空间大小加b2_chunkArrayIncrement,重新申请空间,并将空间的内存拷贝到现在的空间中,并释放原内存空间。
e)、通过m_chunks+m_chunkCount获取块空间的m_chunks动态数组的未被使用的空间元素,申请大小为b2_chunkSize的内存,并将其分成对应类型的n块空间,并将这n个子节点串链起来,形成链表。将链表的下一个指针保存到对应类型的m_freeLists数组中,同时返回头指针作为申请的内存地址。
关于Free函数,代码和注释如下:
void b2BlockAllocator::Free(void* p, int32 size)
{
//判断检测size是否有效
//并作相应的处理
if (size == 0)
{
return;
}
b2Assert(0 < size);
if (size > b2_maxBlockSize)
{
b2Free(p);
return;
}
//根据内存大小获取索引值,并判断是否有效
int32 index = s_blockSizeLookup[size];
b2Assert(0 <= index && index < b2_blockSizes);
#ifdef _DEBUG
// Verify the memory address and size is valid.
int32 blockSize = s_blockSizes[index];
bool found = false;
for (int32 i = 0; i < m_chunkCount; ++i)
{
b2Chunk* chunk = m_chunks + i;
if (chunk->blockSize != blockSize)
{
b2Assert( (int8*)p + blockSize <= (int8*)chunk->blocks ||
(int8*)chunk->blocks + b2_chunkSize <= (int8*)p);
}
else
{
if ((int8*)chunk->blocks <= (int8*)p && (int8*)p + blockSize <= (int8*)chunk->blocks + b2_chunkSize)
{
found = true;
}
}
}
b2Assert(found);
memset(p, 0xfd, blockSize);
#endif
//获取块的块的头指针并插入到相应的空闲链表的头部【注意是子节点从链表头部插入】,并保存相应的头指针到m_freeLists中去
b2Block* block = (b2Block*)p;
block->next = m_freeLists[index];
m_freeLists[index] = block;
}
void b2BlockAllocator::Clear()
{
//释放当前已使用的块空间大小
for (int32 i = 0; i < m_chunkCount; ++i)
{
b2Free(m_chunks[i].blocks);
}
m_chunkCount = 0;
//清空块
memset(m_chunks, 0, m_chunkSpace * sizeof(b2Chunk));
//清空未被使用的内存块链表类型数组
memset(m_freeLists, 0, sizeof(m_freeLists));
}
ok,不多说了,有什么错误、不妥之处,希望大家能多多指正。也希望和大家多多交流。