Box2D源码学习(3)-b2StackAllocator栈内存分配

上一篇文章中的b2BlockAllocator,作为从堆上分配小块内存,它让众多的小对象的创建销毁更加便捷。但同样还不够完善,作为从堆上分配的内存,使用时需要临时分配。使用完需要销毁。而如果该块内存只是在一个时间步有用到,岂不是很不划算。

栈内存能够比较好的解决这个问题——stack。看一下栈和堆的区别:

1、管理方式不同:栈由编译器管理;堆由程序员管理。
2
、空间大小不同:win32中,堆可达4GVC中栈默认1M(可以修改)。
3
、碎片问题:堆易产生;栈不会。
4
、生长方向不同:堆生长方向是向上的,也就是向着内存增加的方向;栈相反。
5
、分配方式不同:堆是动态的,没有静态的堆;栈有两种:动态和静态。
6
、分配效率不同:栈,系统提供底层支持,有专门的寄存器存放栈地址,效率高;堆,由库函数提供支持,效率底。

 

由于栈是在编译期间分配好的,不会动态的去改变。就像我们C语音中使用的数组一样。对于那种生命期很短的对象,直接在栈上保存,显然更加快捷。于是Box2D中便有了b2StackAllocator

还是先看头文件,b2StackAllocator.hpublic的方法除了构造及析构函数。还有:

        void* Allocate(int32 size);

        void Free(void* p);

        int32 GetMaxAllocation() const;

分别是用来分配内存,释放内存,获得历史上最多被分配过多大内存

private的属性则比较多,最重要的就是栈内存的空间啦.

使用   char m_data[b2_stackSize];来作为保存栈内存的容器。(果然是定义了一个大数组,然后在这段数组里做文章;)b2_stackSize就是编译时将会分配的栈内存大小。在这里为b2_stackSize = 100 * 1024;  // 100k

有意思的是这段内存保存在栈上,这个栈的意思是栈内存,是由系统自动维护的内存区域。不会由用户去操作。而在数据结构的概念上,同样有个栈结构,上面都说的栈内存是栈结构的系统实现,不过在b2StackAllocator中,对内存的AllocateFree,也是按照栈结构去进行的。也就是后进先出。

属性   int32 m_index;代表当前m_data数据被分配掉的内存数目,

 int32 m_allocation;代表的也是被分配掉的内存数目,和m_index有所区别的是,当m_data栈上内存不够用的时候,b2StackAllocator会从堆内存上分配一部分内存来使用,这个m_allocation也就会不管你是哪里的内存,都会被统计,m_index则只会统计m_data这段栈上的内存。

int32 m_maxAllocation;历史上最多被分配过多大内存,也就是GetMaxAllocation函数返回的值。在源代码中实际并没有用到这个函数和属性。

        b2StackEntry m_entries[b2_maxStackEntries]; b2_maxStackEntries是个全局量,为32,意味着这个栈内存分配器最多分配32组内存。m_entries保存着每次分配的内存的入口,也就是内存头部的地址。而b2StackEntry是一个结构体。

struct b2StackEntry

{

        char* data;

        int32 size;

        bool usedMalloc;

};

分别是每次分配的内存首地址,这块内存的大小,和这块内存是否用到了从堆上来分配。

 int32 m_entryCount;代表当前分配了多少组内存。

这里欠个示意图,来说明整个b2StackAllocator中的各个属性什么意思。

 

b2StackAllocator.cpp

当然就是上面说的各个函数的实现啦。

构造函数就是把几个计数的参数初始化为零。

        m_index = 0;

        m_allocation = 0;

        m_maxAllocation = 0;

        m_entryCount = 0;

析构函数由于内存是在栈上,所以也没必要去释放内存。(部分在堆上分配的内存以及被free释放掉了),所以析构函数就声明了一下之前分配的内存,的确已经都被free了。(所以Allocatefree要记得成对使用)

 b2Assert(m_index == 0);

 b2Assert(m_entryCount == 0);

Allocate(int32 size)函数是本节的重点啦。思路呢,就是从那100K栈上的内存中,拿出size这么大内存,然后被b2StackEntry m_entries[b2_maxStackEntries];记录在案,每分配一次,m_entryCount计数增加一次。如果栈上内存不够了,就从堆上malloc一块内存,并且把这块内存标记为“被分配的”——usedMalloc=true。相对来说,代码就不那么重要了,照着这个思路去对照代码相信都可以看明白。不过还是把代码贴在这吧。

void* b2StackAllocator::Allocate(int32 size)

{

  b2Assert(m_entryCount < b2_maxStackEntries);

 

  b2StackEntry* entry = m_entries + m_entryCount;

  entry->size = size;

  if (m_index + size > b2_stackSize)

  {

           entry->data = (char*)b2Alloc(size);

           entry->usedMalloc = true;

  }

  else

  {

           entry->data = m_data + m_index;

           entry->usedMalloc = false;

           m_index += size;

  }

 

  m_allocation += size;

  m_maxAllocation = b2Max(m_maxAllocation, m_allocation);

  ++m_entryCount;

 

  return entry->data;

}


 

Free(void* p)有点局限性,它不会去p这个位置找分配了多少内存,然后释放掉它,而是去栈中先去找最近分配的这块内存,如果这块内存就是p这块内存的话,就释放它。说了这么多,其实就是栈的使用方法了。局限就在于,你先后Allocate3块内存。哪么释放的时候,就一定要按照相反顺序来释放这3块。如果顺序错了,就没法释放掉。这个地方会有个疑问,那就是既然都按照顺序,又何必还要这个指针p呢,直接free()不就好了,就像pop一样,根本不用什么实参了。其实是可以的,至少从源码上,是可以这么修改的。但是这里要这么定义,我擦还是为了使用的方便吧,free(xx)至少知道你是释放的哪个对象。当你心里要释放的对象和实际释放的对象不符时,能够报错。而不是free()稀里糊涂就把最近分配的内存释放掉。

具体释放时候的策略,则是判断如果是从堆上分配的内存,则把堆内存释放掉。如果不是的话,更方便了。直接讲栈内存计数m_index减去这段内存的大小。这样下次需要Allocate()的时候,又会从m_index开始分配,之前释放的这块内存实际上还是会保存以前的东西,这有在下次分配并使用的时候,才会被新的数据覆盖。

void b2StackAllocator::Free(void* p)

{

  b2Assert(m_entryCount > 0);

  b2StackEntry* entry = m_entries + m_entryCount - 1;

  b2Assert(p == entry->data);

  if (entry->usedMalloc)

  {

           b2Free(p);

  }

  else

  {

           m_index -= entry->size;

  }

  m_allocation -= entry->size;

  --m_entryCount;

 

  p = NULL;

}


 

还有个GetMaxAllocation则是直接返回m_maxAllocation值,暂时不知道用处。当然,源码中也没用它。

int32 b2StackAllocator::GetMaxAllocation() const

{

 return m_maxAllocation;

}

 

 应用实例:

b2ContactSolver类的构造函数中先后分配了2块栈内存。(还没看到这块,所以不知道这个类啥用)

m_positionConstraints = (b2ContactPositionConstraint*)m_allocator->Allocate(m_count * sizeof(b2ContactPositionConstraint));
      m_velocityConstraints = (b2ContactVelocityConstraint*)m_allocator->Allocate(m_count * sizeof(b2ContactVelocityConstraint)); 

 

 然后会在内存m_positionConstraints中存储b2ContactPositionConstraint的对象数据。

b2ContactPositionConstraint* pc = m_positionConstraints + i;
  pc->indexA = bodyA->m_islandIndex;
  pc->indexB = bodyB->m_islandIndex;
  pc->invMassA = bodyA->m_invMass;
  pc->invMassB = bodyB->m_invMass;
  pc->localCenterA = bodyA->m_sweep.localCenter;
  pc->localCenterB = bodyB->m_sweep.localCenter;
  pc->invIA = bodyA->m_invI;

 

使用完后释放

 m_allocator->Free(m_velocityConstraints);
       m_allocator->Free(m_positionConstraints);

注意释放顺序和分配顺序相反。

 

 

 

你可能感兴趣的:(C++,box2D)