您的位置:首页 > 编程语言 > C语言/C++

c/c++中的内存分配器

2017-07-20 15:37 519 查看
近日为新员工准备技术讲演稿ppt,搜索了一下c/c++中的内存分配器。看到下面这个博客说的涵盖的点较为多,转帖过来。

本博客转载自http://blog.csdn.net/ybt631/article/details/6863229 对原作者表示感谢!

   对于C++开发而言,内存分配优化几乎每个项目优化的必须课题.其实现方式也是五花八门.本文重点总结下这方面的经验.

1.通用分配/释放的优化

对于windows应用程序的内存分配 

,从上层往下,以此是malloc/new-->HeapAlloc-->VirtualAlloc.通用的内存分配优化,一般会选择基于VirtualAlloc重新实现malloc.以期替代c标准函数malloc.

现在开源的这样的实现,效率比较好的有tcmalloc和nedmalloc.前者是google的众多基本库之一.后者是历史悠久的一个malloc开源实现.他们的实现原理基本一致,TLS+struct MemPool{ struct MemPool* next;}这样的结构实现.其中实现的大部分代码在于如何构建内存地址,以便在free时通过地址获得它的大小.就性能而言,它们也基本一致.

不过tcmalloc有个好处,如果链接了它的dll,会自动hook所有分配函数,并替换之.能够非
常方面的集成到程序中.性能来说,它比默认的分配快很多.在我们开发的游戏,集成后在某些机器上能带来30%以上的fps提升.

 

2.Free带大小的分配和释放

虽然tcmalloc有很好的表现,但是在某些情况下,比如stl容器,和每次只分配一个的对象,他们在free时,会带上释放内存的大小.

这样我们可以简化tcmalloc实现,直接每个分配大小映射一个Mem链表,多线程依然采用TLS解决.典型的逻辑大概如下:

Void * fast_malloc(size_t sz)

{

// g_mainList采用TLS存储.对于过大尺寸的内存分配采用默认分配.

MemPool*& pHead = g_mainList[sz];

If(!pHead )

{

// 初始化链表

}

MemPool* pRet = pHead;

pHead = pHead->next;

Return pRet;

}

Void  fast_free(void* p, size_t sz)

{

MemPool* pNew = (MemPool*)p;

MemPool*& pHead = g_mainList[sz];

pNew->next = pHead;

pHead = pNew;

}

这样实现后,在内存分配/释放分别在不同线程的情况下,会造成内存泄漏.可以针对每个大小的链表添加一个计数,超过一定个数就统一释放.一般来说这个会比tcmalloc还快20%.

3.Std::map/list等特殊容器的适配器优化

map/list这样的容器,总是一次分配一个结点.对于他们的适配器,可以更特殊的处理.

这样大概有两个成员变量.

template<class Ty>

class FastOneAllocator

{

MemPool*m_poolHead;

// 记录分配的内存,方便最终释放

MemPool*m_ListHead;     

   // 内存链.分配/释放原理跟上文类似

};

释放/分配的逻辑如下:

pointer allocate(size_type _Count)

{

 
                   if(m_ListHead == NULL)

 
                    {

//有个默认条件:sizeof(Ty)必须大于等于sizeof(MemPool)

 
              void* pNew = malloc(COUNT_PER*sizeof(Ty)+sizeof(MemPool))

//todo: 把内存通过链表连起来.

MemPool* pNewHead = (MemPool*)pNew;

pNewHead->next = m_poolHead;

m_poolHead = pNewHead;

 
                     }

   
         // 同fast_malloc逻辑.

}

//析构函数内.

~FastOneAllocator()

{

MemPool* lastPtr = NULL;

 
                   for(; m_poolHead; m_poolHead = lastPtr)

 
                  {

 
                       lastPtr = m_poolHead->_next;

 
             free(m_poolHead);

}

}

如果list以FastOneAllocator作为适配器.list::swap就会问题.

典型的调用如下:

list<int> t2;

{

 list<int> t1;

 t1.push_back(1);

  t2.swap(t1); 

   //t1的适配器析构 把交换过去的t2内存都释放了 

   }

类似list这样的数据结构,都是通过一个head来把所有容器内数据串联起来的.list::swap主要是通过直接交换head来达到swap整个数据结构的目的.

为解决这个问题,可以统一把list::swap用std::swap实现.这里效率相对来说有所降低.但是考虑到这样的调用实际中并不常见,也是可以接受的。

实际测试的结果, FastOneAllocator相对于以fast_malloc为基础构建的适配器,快15%左右.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: