如何在我的C ++程序中检测和估计堆碎片?

时间:2009-10-19 14:16:53

标签: c++ visual-c++ memory heap

我正在开发一个VC ++ NT服务,该服务旨在连续运行数月。它密集使用VC ++运行时堆。很明显,堆碎片在某些时候会导致它出现故障(认为它是内存不足)。

我可以在我的服务上运行哪些测试来估计容易出现堆碎片的程度?

6 个答案:

答案 0 :(得分:5)

你已经得到了几个答案,讨论如何防止堆碎片问题,但两者都没有直接解决你的问题。几乎唯一能够估计出现碎片问题的可能性的方法是模拟大量使用,并测量你得到的碎片。

由于这是一项NT服务,因此模拟使用数月主要包括匆忙提出大量请求。有可能您可以比通常预期的更快地发出请求,因此您可以在几个小时内模拟几个月的请求,甚至可能更少(取决于您通常期望接收请求的速率) )。

一旦你模拟了几个月的工作(或者甚至在你这样做的时候),你需要查看堆,看看你得到了多少碎片。这并不容易,但通常是可能的。您将首先在服务进程中注入一个线程(在线程注入上搜索“或者该命令中的某些内容应该获得相当多的信息)。然后你需要走遍堆,寻找(特别是)可用的块,但是太小而不能满足大多数请求。假设您正在使用MS VC ++,那么您将使用_heapwalk遍历堆,并且它将遍历堆,告诉您堆中每个块的地址,大小和状态(空闲或正在使用)。

最后一个细节:为了产生有意义的结果,包含注入线程的可执行文件和DLL都必须链接到DLL中的运行时库。这意味着整个进程将有一个堆,因此您注入的线程将遍历您的服务使用的堆。如果静态链接标准库,则DLL和服务都将拥有自己的堆。 DLL将遍历自己的堆,它不会告诉您服务进程正在使用的堆。

答案 1 :(得分:2)

我想最好的方法是编写自己的内存管理器(或购买一台)来提供这些数据。任何其他方式都会改变堆本身,从而使结果无效。

更容易实现的策略是分配不同大小的内存块并等待失败 - 但我认为这不是一个好方法。无论如何 - 块大小越大,没有失败,碎片越少。但是根据内存管理器的不同,分配块可能会改变结果。


编辑:我找到了一个关于slab分配器(注释的thx)的链接,显示了统计信息。虽然它是德语,但该文章的英文版本并未包含太多信息。使用babelfish进行翻译。

http://de.wikipedia.org/wiki/Slab_allocatorbabelfish version

http://www.usenix.org/event/usenix01/full_papers/bonwick/bonwick.pdf

答案 2 :(得分:1)

在Windows的低碎片堆上切换可以帮助在旧系统上完成工作。 在新系统上它的默认开启(Vista,Server 2008)

  HANDLE heaps[1025];
  DWORD nheaps = GetProcessHeaps((sizeof(heaps) / sizeof(HANDLE)) - 1, heaps);
  for (DWORD i = 0; i < nheaps; ++i) {
    ULONG  enableLFH = 2;
    HeapSetInformation(heaps[i], HeapCompatibilityInformation, &enableLFH, sizeof(enableLFH));
  }

sysinternals(现在是Microsoft)提供了一个工具VMMap,它可以很好地概述内存碎片。

答案 3 :(得分:1)

检测碎片的最简单方法是确定您的程序将进行的最大分配,然后每次分配至少两倍的数量。如果分配失败,则返回NULL并且您的堆使用由代码确定 - 在Windows上就像这样

PROCESS_MEMORY_COUNTERS counters;
if(GetProcessMemoryInfo(process, &counters, sizeof(counters))){
    result = counters.WorkingSetSize;
}   

小于系统内存的某个百分比,通常为75%,那么你肯定会出现碎片问题。

答案 4 :(得分:0)

我同意Tobias - 让自己的内存管理器成为一个很好的方法。我知道只有少数开发人员会信任我写这种代码......

另一种可能性是在你的对象上不时地进行自己的垃圾收集/整合 - 在低负载下...即你的服务可以暂时不活动,同时它“碎片化”它使用的内存但是我我不确定如果没有自己的内存管理,你可以保证你想要的行为。

答案 5 :(得分:0)

我确信有一些Windows工具可以为您提供内存状态,但是您应该考虑到这个问题来开发服务。

首先,你应该了解你预先形成的分配。我认为这样做的简单方法是覆盖new和delete运算符,并且从这些新运算符中,您应该计算一些分配的统计信息,然后调用编译器的默认new和delete运算符。

我认为您应该计算的最小统计数据是公共块大小范围的分配数量。

e.g。 0字节到15字节之间的块,16字节到32字节之间的块,32字节到48字节之间的块,......

您还可以添加每个块大小范围的顺序分配数

收集此数据后,您可以通过将块与常规尺寸对齐来减少碎片问题。

最佳和简单的对齐技术是使用功率为2的块。

例如,要将数字与最接近16的数字对齐,可以使用以下函数:

int align(int size)
{
    return ((size + 15) & ~0x0000000F);
}

当然,你应该使用你的统计数据来选择2的最佳幂来对齐。 目标是达到一个数字,大多数分配将进入几个块范围,同时保持对齐的开销合理。

祝你好运......