49

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

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

1)使用Windows低碎片堆

2) jemalloc - 用于Firefox 3

3) Doug Lea 的malloc

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

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

4

10 回答 10

38

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

希望您当前使用的堆管理器能够转储堆中的实际可用总空闲空间(跨所有空闲块)以及它被划分的块总数。如果平均空闲块大小与堆中的总空闲空间相比相对较小,那么您确实存在碎片问题。或者,如果您可以转储最大空闲块的大小并将其与总空闲空间进行比较,那将完成同样的事情。如果您遇到碎片,最大的空闲块相对于所有块中可用的总可用空间将很小。

要非常清楚上述内容,在所有情况下,我们都在谈论堆中的空闲块,而不是堆中分配的块。无论如何,如果不满足上述条件,那么您确实存在某种泄漏情况。

因此,一旦您排除了泄漏,您可以考虑使用更好的分配器。问题中建议的Doug Lea 的 malloc对于一般用途的应用程序来说是一个非常好的分配器,并且在大多数情况下非常健壮。换句话说,它已经过时间测试,可以很好地适用于大多数应用程序。然而,没有一种算法适用于所有应用程序,任何管理算法方法都可能被与其设计相反的正确病理条件破坏。

为什么你有碎片问题?- 碎片问题的来源是由应用程序的行为引起的,并且与同一内存区域中的分配生命周期大不相同。也就是说,一些对象会定期分配和释放,而其他类型的对象会在同一个堆中持续较长时间......将较长生命周期的对象视为在竞技场的更大区域中戳洞,从而防止合并已释放的相邻块。

要解决此类问题,您可以做的最好的事情是在逻辑上将堆划分为生命周期更相似的子区域。实际上,您需要一个临时堆和一个或多个持久堆,它们将具有相似生命周期的事物组合在一起。

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

此外,使用像 Doug Lea 这样的好的堆分配器,使块大小更相似是不必要的,因为分配器已经在执行两倍大小的分桶方案,这将完全没有必要人为地调整传递给 malloc( ) - 实际上,他的堆管理器会自动为您执行此操作,比应用程序能够进行的调整要强大得多。

于 2008-09-14T02:12:23.013 回答
15

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

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

这似乎是一个很小的泄漏,仅在监视分配的总内存大小时几乎不会注意到它。但是这种泄漏最终会导致您的应用程序内存看起来像这样:
.
.
免费 – 10MB

.
[分配的 -1 字节]

.
免费 – 10MB

.
[分配的 -1 字节]

.
免费 – 10MB

.

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

于 2008-10-12T10:46:16.837 回答
5

正如您所建议的那样,Doug Lea 的 malloc 可能效果很好。它是跨平台的,并且已用于运输代码。至少,它应该很容易集成到您的代码中进行测试。

在固定内存环境中工作多年,这种情况肯定是个问题,即使在非固定环境中也是如此。我们发现 CRT 分配器在性能(速度、浪费空间的效率等)方面往往很糟糕。我坚信,如果您在很长一段时间内广泛需要一个好的内存分配器,您应该编写自己的(或者看看像 dlmalloc 这样的东西是否可以工作)。诀窍是编写适合您的分配模式的东西,并且几乎与其他任何事情一样与内存管理效率有关。

试试 dlmalloc。我肯定会竖起大拇指。它也是相当可调的,因此您可以通过更改一些编译时间选项来获得更高的效率。

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

于 2008-09-14T02:21:08.530 回答
2

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

例如,对于运行服务器端脚本的 Web 服务器,它可能会创建一个字符串来将页面输出到。无需为每个页面请求分配和释放这些字符串,只需维护它们的池,因此您仅在需要更多时才分配,但不会释放(意味着一段时间后您会遇到不再分配的情况,因为您有足够)

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

于 2008-09-13T21:21:56.783 回答
1

在怀疑碎片之前,我会怀疑泄漏。

对于内存密集型数据结构,您可以切换到可重用的存储池机制。与堆相比,您还可以在堆栈上分配更多的东西,但实际上我认为这不会产生太大的影响。

我会启动一个像 valgrind 这样的工具,或者做一些密集的日志记录来寻找没有被释放的资源。

于 2008-09-13T21:10:38.230 回答
1

@nsaners - 我很确定问题出在内存碎片上。我们分析了在分配大块(5-10mb)内存时指向问题的小型转储。我们还监控了过程(现场和开发中)以检查内存泄漏 - 没有检测到(内存占用通常非常低)。

于 2008-09-13T21:28:44.363 回答
1

这个问题确实发生在 Unix 上,尽管它通常没有那么糟糕。

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

我们还研究了块/分块分配并尝试使用范围精明的池/策略,即这里的长期事物,那里的整个请求事物,那里的短期事物等。

于 2008-09-13T21:59:32.060 回答
1

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

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

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

于 2008-09-13T22:55:00.333 回答
-1

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

于 2016-11-29T11:09:32.023 回答
-1

简单、快速和肮脏的解决方案是将应用程序拆分为多个进程,每次创建进程时都应该获得新的 HEAP。

您的内存和速度可能会受到一点影响(交换),但快速硬件和大 RAM 应该能够提供帮助。

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

于 2018-11-03T17:40:21.817 回答