如何在我的 C++ 程序中检测和估计堆碎片?
Posted
技术标签:
【中文标题】如何在我的 C++ 程序中检测和估计堆碎片?【英文标题】:How to detect and estimate heap fragmentation in my C++ program? 【发布时间】:2010-12-08 00:25:10 【问题描述】:我正在开发一个 VC++ NT 服务,该服务旨在连续运行数月。它大量使用 VC++ 运行时堆。显然,堆碎片在某些时候会导致它出现故障(认为它内存不足)。
我可以在我的服务上运行哪些测试来估计它容易出现堆碎片的程度?
【问题讨论】:
【参考方案1】:您已经获得了一些关于如何防止堆碎片问题的答案,但都没有真正直接解决您的问题。估计遭受碎片问题的可能性的几乎唯一方法是模拟大量使用,并测量您得到的碎片。
由于它是一项 NT 服务,因此模拟几个月的使用主要包括快速发出大量请求。您可能会比通常预期的接收请求更快地发出请求,因此您可能只需要几个小时就可以模拟几个月的请求,甚至可能更少(取决于您通常期望接收请求的速率)。
一旦您模拟了几个月的工作价值(或者即使您正在这样做),您就需要查看堆以了解您获得了多少碎片。这并不容易,但通常是可能的。您将从将线程注入服务进程开始(谷歌搜索“线程注入”或该订单上的某些内容应该会获得相当多的信息)。然后你需要遍历堆,寻找(特别是)空闲的块,但太小而无法满足大多数请求。假设您使用的是 MS VC++,您使用 _heapwalk 遍历堆,它会遍历堆,告诉您堆中每个块的地址、大小和状态(空闲或使用中)。
最后一个细节:为了产生有意义的结果,可执行文件和包含注入线程的 DLL 都必须链接到 DLL 中的运行时库。这意味着整个过程将有一个堆,因此您注入的线程将遍历您的服务正在使用的堆。如果静态链接标准库,DLL 和服务将各自拥有自己的堆。 DLL 将遍历它自己的堆,它不会告诉您有关服务进程正在使用的堆的任何信息。
【讨论】:
【参考方案2】:我想最好的方法是编写自己的内存管理器(或购买一个)来提供这些数据。任何其他方式都会改变堆本身,从而使结果无效。
一个更容易实现的策略是分配不同大小的内存块并等待失败——但我认为这不是一个好方法。无论如何 - 块大小越大,没有失败,碎片就越少。但是根据内存管理器,分配块可能会改变结果。
编辑:我找到了一个关于平板分配器的链接(感谢评论)显示统计信息。虽然它是德文的,但英文版的文章没有包含那么多信息。使用 babelfish 进行翻译。
http://de.wikipedia.org/wiki/Slab_allocator (babelfish version)
http://www.usenix.org/event/usenix01/full_papers/bonwick/bonwick.pdf
【讨论】:
但是更换内存管理器只是为了测量它是没有用的,除非你打算在生产中使用那个内存管理器。我认为更好的解决方案是检测实际的内存分配器并测量其碎片。您可以通过包装 alloc/free 调用来做到这一点,或者您可以查看内存管理器是否有可以使用的挂钩。【参考方案3】:为 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,它可以很好地概述内存碎片。
【讨论】:
【参考方案4】:检测碎片的最简单方法是确定程序将进行的最大分配,然后不时分配至少两倍的数量。如果分配失败,即返回 NULL 并且您的堆使用情况由代码确定 - 在 Windows 上类似这样
PROCESS_MEMORY_COUNTERS counters;
if(GetProcessMemoryInfo(process, &counters, sizeof(counters)))
result = counters.WorkingSetSize;
小于系统内存的某个百分比,通常为 75%,那么您肯定有碎片问题。
【讨论】:
【参考方案5】:我同意 Tobias 的观点 - 制作自己的内存管理器是实现此目的的绝佳方式。我知道只有少数我信任的开发人员会编写这种代码……
另一种可能性是不时对您的对象进行自己的垃圾收集/合并 - 在低负载下......即您的服务可能会在一段时间内处于非活动状态,而它会“整理”它使用的内存,但我如果没有自己的内存管理,我不确定你是否可以保证你想要的行为。
【讨论】:
【参考方案6】:我确信有一些 Windows 工具可以为您提供内存状态,但您应该在开发服务时考虑到这个问题。
首先,您应该了解您执行的分配是什么。我认为最简单的方法是重写 new 和 delete 运算符,从这些 new 运算符中计算分配的一些统计信息,然后调用编译器的默认 new 和 delete 运算符。
在我看来,您应该计算的最小统计数据是常见块大小范围的分配数。
例如 0 字节到 15 字节之间的块,16 字节到 32 字节之间的块,32 字节到 48 字节之间的块,...
您还可以添加每个块大小范围的顺序分配数
收集这些数据后,您可以通过将块对齐到常见大小来减少碎片问题。
最好和最简单的对齐技术是使用 2 次方的方块。
例如,要将数字与除以 16 的最接近的数字对齐,您可以使用以下函数:
int align(int size)
return ((size + 15) & ~0x0000000F);
当然,您应该使用统计数据来选择 2 的最佳幂以与之对齐。 目标是达到一个数字,即您的大多数分配将进入几个块范围,同时保持合理的对齐开销。
祝你好运……
【讨论】:
以上是关于如何在我的 C++ 程序中检测和估计堆碎片?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Dlib C++ 中获取头部姿势估计的 3D 坐标轴