如何解决内存碎片问题

时间:2008-09-13 21:04:08

标签: c++ windows memory

由于内存分配失败,我们偶尔遇到问题,因为我们长时间运行的服务器进程(在Windows Server 2003上运行)引发了异常。我们怀疑这些分配由于内存碎片而失败。

因此,我们一直在寻找一些可能对我们有帮助的替代内存分配机制,我希望有人能告诉我最好的内容:

1)使用Windows Low-fragmentation Heap

2)jemalloc - 用于Firefox 3

3)Doug Lea的malloc

我们的服务器进程是使用跨平台的C ++代码开发的,因此任何解决方案都可以理想地跨平台(do * nix操作系统会遭受这种类型的内存碎片吗?)。

另外,我认为LFH现在是Windows Server 2008 / Vista的默认内存分配机制吗?如果我们的客户只是升级他们的服务器操作系统,我当前的问题会“消失”吗?

10 个答案:

答案 0 :(得分:34)

首先,我同意其他提出资源泄漏的海报。你真的想先排除它。

希望您当前使用的堆管理器有一种方法可以转储堆中可用的实际总可用空间(跨所有 free 块)以及它的总块数分开了。如果平均空闲块大小与堆中的总可用空间相比相对较小,那么您确实存在碎片问题。或者,如果您可以转储最大空闲块的大小并将其与总可用空间进行比较,那么这将完成相同的任务。如果您遇到碎片,则相对于所有块中可用的总 free 空间,最大的空闲块将很小。

要清楚上述内容,在所有情况下,我们都在讨论堆中的 free 块,而不是堆中已分配的块。在任何情况下,如果不满足上述条件,那么会出现某种泄漏情况。

所以,一旦你排除了泄漏,你可以考虑使用更好的分配器。问题中建议的 Doug Lea的malloc 是一个非常好的通用应用程序分配器,非常强大的大多数。换句话说,它经过时间测试,可以很好地适用于大多数应用程序。但是,对于所有应用程序而言,没有任何算法是理想的,并且任何管理算法方法都可以通过针对其设计的正确的语法条件来打破。

为什么会出现碎片问题? - 碎片问题的来源是由应用程序的行为导致导致,并且在同一行程中与分配生命周期大不相同记忆竞技场。也就是说,一些对象被定期分配和释放,而其他类型的对象在同一堆中持续很长一段时间......想想更长寿命的对象是在竞技场的更大区域戳洞,从而防止已释放的相邻块的合并。

要解决这类问题,你可以做的最好的事情就是将逻辑上的堆划分为生命周期更相似的子竞技场。实际上,您需要一个临时堆和一个持久堆或堆,它们将相似生命周期组合在一起。

其他一些人提出了另一种解决问题的方法,即尝试使分配大小更相似或相同,但这不太理想,因为它会产生一种称为内部碎片的不同类型的碎片 - 这实际上是浪费通过在块中分配比您需要的更多内存而占用的空间。

此外,如果像Doug Lea那样有一个好的堆分配器,那么块大小更相似是不必要的,因为分配器已经在做两个大小的分段方案的功能,这将使它完全没有必要人为地调整传递的分配大小to malloc() - 实际上,他的堆管理器会自动为您执行此操作,而不是应用程序可以进行调整。

答案 1 :(得分:15)

我认为你错误地排除了过早的内存泄漏。 即使是微小的内存泄漏也会导致严重的内存碎片。

假设您的应用程序的行为如下:
分配10MB
分配1个字节
免费10MB
(哎呀,我们没有释放1个字节,但谁关心1个小字节)

这似乎是一个非常小的泄漏,在监控总分配内存大小时几乎不会注意到它。 但这种泄漏最终会导致您的应用程序内存看起来像这样:


免费 - 10MB


[分配-1字节]


免费 - 10MB


[分配-1字节]


免费 - 10MB

点。

这个泄漏不会被注意到...直到你想分配11MB
假设您的minidump包含完整的内存信息,我建议使用DebugDiag来发现可能的泄漏。 在生成的内存报告中,仔细检查分配计数(不是大小)

答案 2 :(得分:5)

如你所知,Doug Lea的malloc可能运作良好。它是跨平台的,已用于运输代码。至少,它应该很容易集成到您的代码中进行测试。

在固定内存环境中工作了很多年,即使在非固定环境中,这种情况也是一个问题。我们发现CRT分配器在性能(速度,浪费空间效率等)方面往往很糟糕。我坚信如果你在很长一段时间内都需要一个好的内存分配器,你应该自己编写(或者看看像dlmalloc这样的东西是否可行)。诀窍在于可以编写适合您的分配模式的内容,这与内存管理效率几乎完全相同。

尝试dlmalloc。我绝对赞不绝口。它也相当可调,因此您可以通过更改一些编译时选项来提高效率。

老实说,你不应该依赖于新的操作系统实现“消失”的东西。 N年后的服务包,补丁或其他新操作系统可能会使问题变得更糟。同样,对于需要强大内存管理器的应用程序,请不要使用编译器可用的库存版本。找一个适合你的情况的人。从dlmalloc开始并调整它以查看是否可以获得最适合您情况的行为。

答案 3 :(得分:2)

您可以通过减少分配deallocate的数量来帮助减少碎片。

e.g。对于运行服务器端脚本的Web服务器,它可能会创建一个字符串来输出页面。不是为每个页面请求分配和释放这些字符串,而只是维护它们的池,所以你需要更多时才进行分配,但是你没有解除分配(意味着一段时间后你得到你不再分配的情况,因为你有足够)

你可以使用_CrtDumpMemoryLeaks();在运行调试版本时将内存泄漏转储到调试窗口,但我相信这是特定于Visual C编译器的。 (它在crtdbg.h中)

答案 4 :(得分:1)

在怀疑碎片之前,我怀疑是否有泄漏。

对于内存密集型数据结构,您可以切换到可重用的存储池机制。你可能也可以在堆栈上分配更多的东西而不是堆,但实际上我认为不会产生巨大的差异。

我会启动像valgrind这样的工具,或者进行一些密集的日志记录来查找未被释放的资源。

答案 5 :(得分:1)

@nsaners - 我很确定问题归结为内存碎片问题。我们已经分析了minidumps,指出在分配大量(5-10mb)内存块时出现问题。我们还监视过程(现场和开发中)以检查内存泄漏 - 没有检测到(内存占用通常非常低)。

答案 6 :(得分:1)

问题确实发生在Unix上,虽然它通常不那么糟糕。

Low-framgmentation堆帮助了我们,但我的同事发誓Smart Heap (它已经在我们的几个产品中使用跨平台多年)。不幸的是,由于其他情况我们这次不能使用Smart Heap。

我们还会看看块/块分配并尝试拥有精通范围的池/策略,即 这里的长期事情,那里的整体要求,那里的短期事情等等。

答案 7 :(得分:1)

像往常一样,你通常可以浪费内存来获得一些速度。

这种技术对于通用分配器没用,但确实有它的位置。

基本上,我们的想法是编写一个从池中返回内存的分配器,其中所有分配的大小都相同。这个池永远不会碎片化,因为任何块都和另一块一样好。您可以通过创建具有不同大小块的多个池来减少内存浪费,并选择仍然大于请求量的最小块大小池。我已经用这个想法来创建在O(1)中运行的分配器。

答案 8 :(得分:0)

一种简单,快速且肮脏的解决方案是将应用程序拆分为几个过程,每次创建该过程时,您都应该获得新鲜的HEAP。

您的内存和速度可能会有所下降(交换),但是快速的硬件和较大的RAM应该能够提供帮助。

这是带有守护程序的UNIX旧技巧,当时线程还不存在。

答案 9 :(得分:-1)

如果你谈论Win32 - 你可以尝试使用LARGEADDRESSAWARE来挤压一些东西。您将拥有~1Gb额外的碎片整理内存,因此您的应用程序会将其分段更长时间。