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

来源:互联网 发布:linux 多进程读写文件 编辑:程序博客网 时间:2024/06/08 07:04

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


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


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

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

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

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

现在开源的这样的实现,效率比较好的有tcmallocnedmalloc.前者是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()

{

MemPoollastPtr = NULL;

                     for(; m_poolHeadm_poolHead = lastPtr)

                    {

                         lastPtr = m_poolHead->_next;

               free(m_poolHead);

}

}

如果listFastOneAllocator作为适配器.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%左右.

原创粉丝点击