本系列博客是由扭曲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进行大致的了解一遍。不多说,上代码:

<span style="font-size:14px;">//一次分配内存大小
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;
};
</span>

上面文字是对相关字段及方法的注释,我们就不对其进行讲解了。

二、b2BlockAllocator的.c文件

下面我们看该类的具体实现,看b2BlockAllocator.c文件,
1、变量的定义

映入我们眼帘的是一些变量或结构的定义,如下代码:

<span style="font-size:14px;">int32 b2BlockAllocator::s_blockSizes[b2_blockSizes] =
{16,        // 032,        // 164,        // 296,        // 3128,    // 4160,    // 5192,    // 6224,    // 7256,    // 8320,    // 9384,    // 10448,    // 11512,    // 12640,    // 13
};
uint8 b2BlockAllocator::s_blockSizeLookup[b2_maxBlockSize + 1];
bool b2BlockAllocator::s_blockSizeLookupInitialized;struct b2Chunk
{int32 blockSize;b2Block* blocks;
};struct b2Block
{b2Block* next;
};</span>

s_blockSizes       :是申请的块子节点大小类型数组,主要负责将相应大小的子节点分类;
blockSizeLookup:是根据要申请的子节点size获取s_blockSizes数组的索引,并保持到该数组中;
s_blockSizeLookupInitialized:是否已初始化s_blockSizeLookup数组,标志变量,用于只需要初始化很少次数的变量,可以人为控制【但是最好还是不要那么做】,默认值是false,也许大家感到奇怪,这个变量在哪初始化的,大家可以猜猜看。
b2Chunk是块结构体,其中blockSize表示块子节点大小,blocks表示块头指针;
b2Block表示块子节点结构体,next表示下一个块头指针,如果你感觉到很熟悉的话,那就对了,这是典型的链表定义,将会用链表将子节点链接起来。

2、函数的实现
    1)、构造函数和析构函数

接下来就是该类的构造函数和析构函数了,同样我们也看代码。

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的初始化是怎样完成的。

a)、用s_blockSizeLookupInitialized判断s_blockSizeLookup是否已初始化,若没有则进入。大家猜到绿色部分的疑问了没,如果你的答案是编译器,那就恭喜你了。
b)、里面的for循环主要是根据块的大小,将块分类成以上14中类型,并设置索引值,保存到s_blockSizeLookup数组中,j保存的是s_blockSizes数组的索引值。
c)、接着判断j的有效性,这里用的是assert断言,关于assert断言的又不懂的童鞋可以参照维基百科上面的解释http://zh.wikipedia.org/wiki/Assert.h
d)、if/else中i不大于j索引对应的块大小类型的数组,则将j索引的值赋给类型索引数组,例如,j = 0时,i的值可以是 1-16。否则i>j索引对应的块大小类型的数组,则将j索引自增,赋值。上面代码可以简化成:

if(i > s_blockSizes[j])
{++j;
}
s_blockSizeLookup[i] = (uint8)j;

虽然效率差了点:-D,但这样更易于理解。

e)、将标志变量置s_blockSizeLookupInitialized为true,表示已经初始化在析构函数~b2BlockAllocator()我们释放了当前块的每个子节点,和整个块。

2)、内存管理函数内存管理函数分为三个:Allocate分配函数;Free释放函数;Clear清理内存函数

关于Allocate函数,代码如下:

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;
}

同样,_DEBUG宏类的我们不做讨论。这里说明一下,若内存小于等于b2_maxBlockSize时,此时内存空间并没有释放,而是将链接到相应类型的空闲链表中,并且是从链表头部插入此节点的,对此这也是很容易做到的,这也是刚刚申请空间将连续的空间分割,再次链接成链表的原因。

再看Clear函数

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

只是释放了形成链表的块内存,m_chunks和m_freeLists也只是清空其内容,真正释放它们是在上面说的类的析构函数中。

ok,不多说了,有什么错误、不妥之处,希望大家能多多指正。也希望和大家多多交流。

Box2d源码学习二内存管理之SOA的实现相关推荐

  1. caffe源码分析--SyncedMemory 内存管理机制

    caffe源码分析–SyncedMemory 内存管理机制 ​ SyncedMemory 是caffe中用来管理内存分配和CPU.GPU数据及同步的类,只服务于Blob类.SyncedMemory 对 ...

  2. FFMPEG4.1源码分析之 内存管理APIs av_malloc() av_mallocz()

    1  av_malloc() av_malloc() 声明: 所属库:libavutil,该库是ffmpeg的功能库,提供了线程,内存,文件,加密等功能 头文件:libavutil/mem.h 该函数 ...

  3. Box2d源码学习十二b2Collision之碰撞(上)公共部分的实现

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8390560 Box2d中将碰撞部分单独放到几个文件中去 ...

  4. Box2d源码学习一之Box2d简介

    本系列博客是由扭曲45原创,欢迎转载,转载时注明出处,http://blog.csdn.net/cg0206/article/details/8257607 随着智能手机的大量普及,手机的性能也越来越 ...

  5. Netty源码解析之内存管理-PooledByteBufAllocator-PoolArena

      PooledByteBufAllocator是Netty中比较复杂的一种ByteBufAllocator , 因为他涉及到对内存的缓存,分配和释放策略,PooledByteBufAllocator ...

  6. FFMPEG4.1源码分析之 内存管理APIs av_freep() av_free()

    1. av_freep() av_freep() 声明: 所属库:libavutil(lavu),libavutil是ffmpeg的工具类库,本函数是其内存管理类库中的函数 头文件:libavutil ...

  7. Linux内核源码分析之内存管理

    本文站的角度更底层,基本都是从Linux内核出发,会更深入.所以当你都读完,然后再次审视这些功能的实现和设计时,我相信你会有种豁然开朗的感觉. 1.页 内核把物理页作为内存管理的基本单元. 尽管处理器 ...

  8. Python源码解析:内存管理(DEBUG模式)的几个理解点

    写了这多贴子,顺带写点自己的感想吧!其实很多贴子在写的时候很踌躇,比如这次打算写的python内存管理,因为内存管理都比较琐碎,在软件架构里,也是很容易出问题的地方,涉及的细节内容非常多,要写好写明白 ...

  9. erlang底层c定时器设计-Erlang源码学习二

    Erlang底层的定时器实现位于源码的erts/emulator/beam/time.c文件,用时间轮的方式动态添加和删除定时器,结构体名为typedef struct ErtsTimerWheel_ ...

最新文章

  1. sqlserver2008 获取最后插入的id_Python3操作SQL Server2008数据库
  2. 总结PHP中DateTime的常用方法
  3. [Jarvis OJ - PWN]——[61dctf]fm
  4. 远程计算机需要网络级别身份验证,而您的计算机不支持该验证,请联系您的系统管理员或者技术人员来获得帮助...
  5. 传输层(知识架构图)
  6. ubuntu14.04 x86编译upx 3.92 及so加固
  7. MTK 驱动(71)---DDR进行bitflip压力测试
  8. nginx Alphabetical index of variables
  9. 学会“量体裁衣”去赚钱
  10. KingTable 是表格动态列插件
  11. 《深入理解计算机系统》速读提问
  12. 再读《CSS权威指南》
  13. 华硕天选3笔记本电脑WiFi功能消失
  14. 音频处理——音频处理的基本概念
  15. OIer__ZLY__OI计划
  16. RoaringBitmap数据结构以及精确去重UDAF实现
  17. 你真的了解Web Component吗?
  18. 用Python进行数据分析之金融和经济数据应用
  19. 初识MIMO(四):MIMO的接收端检测技术及其仿真
  20. dpi和ppi换算_DPI和PPI的计算公式

热门文章

  1. 微软hackathon_如何参加编码训练营,聚会,赢得Hackathon彻底改变了我的生活
  2. linux snappy 版本,三款新星Linux解决方案:Snappy、Flatpak和AppImage
  3. Tag Archives: 海明距离
  4. html app状态栏,APP设计:(一)app界面常用设计规范
  5. c语言描述abc表达式cba,2015年3月全国计算机二级C语言选择第2套
  6. 原创:Android应用开发记录-Andorid歌词秀(4)完成,含源码
  7. iOS完整App资源收集
  8. 安卓手机的内置传感器
  9. react项目里如何使用阿里字体图标
  10. 人生得意马蹄急,成长的痛,坚持痛并快乐