这段时间项目中遇到了内存碎片的问题,在双核四G的机器上,明明进程的虚拟内存只用了500M左右,但new却会抛出bad_alloc异常,用process explorer查看进程的内存的使用情况,发现虽然virtual memory仅为500M左右,但已分配的地址空间virtual size却已将近2G了,然后又用vmmap查看进程地址空间分配情况,发现堆内存分配占用比很高,而且内存地址中,总是两个reserve中总是夹着一个commit,很显然,这是内存地址空间严重碎片的表征。内存碎片的问题惯常的解决方法是使用内存池,最简单的内存池就是预先分配好大块连续内存,然后使用时拆分成最频繁使用固定大小的小块内存。但是我们这个项目中由于用到一些比较大且较复杂的开源库,要想定位相关代码的位置,无论从时间和精力上都比较困难。所以,我决定从运行时库标准内存分配例程上着手。原本的方法是实现一个内存池,然后替换掉crt标准的内存分配例程。然而,阅读crt内存相关代码发现,malloc实际是通过windows堆api实现的,我们知道windows堆架构分为前端和后端两部分,前端的堆分为两种,一种是旁查列表实现的性能较高的LAL堆,还有一种是LFH堆。LFH堆顾名思义就是底碎片堆,而LFH对于频繁分配小内存块的情形可以有效降低内存碎片。而crt中使用的crt堆默认是LAL堆,于是抱着姑且一试的心态,我将crt堆设成了LFH堆,再次运行程序,惊喜的发现虚拟内存地址空间的利用效率接近100%,原本进程中只能保存500个会话,现今可以保存5000个会话,足以满足项目需求。下面是我写的一个简单设置LFH堆类型的代码:
BOOL SetLowFragmentHeaps()
{
enum {
LFHHeap = 2
};
DWORD dwHeapNum = GetProcessHeaps(0, NULL);
HANDLE* pHeapHandles = new HANDLE[dwHeapNum];
DWORD dwNum = GetProcessHeaps(dwHeapNum, pHeapHandles);
ULONG lHeapType = LFHHeap;
BOOL bSuccess = TRUE;
HANDLE hCrtHeap = (HANDLE)_get_heap_handle();
while ( --dwNum )
{
BOOL bRet = HeapSetInformation( pHeapHandles[dwNum],
HeapCompatibilityInformation,
&lHeapType, sizeof(lHeapType) );
// 对于crt堆,必须设为LFH堆!
if ( !bRet && hCrtHeap == pHeapHandles[dwNum] )
{
bSuccess = FALSE;
break;
}
}
delete[] pHeapHandles;
return bSuccess;
}
最后谈一下对自定义内存池的看法,很多人(包括之前的我)都一直认为要想实现高效的内存分配,就得自己实现一个内存池,潜意识里觉得系统预提供的设施不足以满足我们的性能需求,但是就我遇到的所有情形而言,系统的提供设施(无论是锁还是内存池或是其他)往往要优于自己的所谓的高效实现,经过百般调优的系统设施在绝大多数情形下足以满足我们的需求,所以当有系统为我们提供好高效健壮的实现,又何必要自己重新发明轮子呢?
----------------------------------------------
作者 hanzz2007 , 转载请注明出处