64 位大型 malloc

Posted

技术标签:

【中文标题】64 位大型 malloc【英文标题】:64 bit large mallocs 【发布时间】:2010-10-24 09:51:41 【问题描述】:

malloc() 失败的原因是什么,尤其是在 64 位中?

我的具体问题是试图在 64 位系统上分配 10GB 的巨大 RAM 块。 这台机器有 12GB 的 RAM 和 32GB 的交换空间。 是的,malloc 是极端的,但为什么会有问题呢?这是在带有 Intel 和 MSFT 编译器的 Windows XP64 中。 malloc 有时会成功,有时不会,大约 50%。 8GB malloc 总是有效,20GB malloc 总是失败。如果 malloc 失败,重复的请求将不起作用,除非我退出该进程并再次启动一个新进程(然后将有 50% 的成功率)。没有其他大型应用程序正在运行。它甚至在重新启动后立即发生。

如果您用完了可用的 32(或 31)位地址空间,我可以想象 malloc 在 32 位中失败,这样就没有足够大的地址范围来分配给您的请求。

如果您用完物理 RAM 硬盘交换空间,我也可以想象 malloc 会失败。对我来说不是这样。

但是为什么 malloc 会失败呢?我想不出其他原因。

我对一般 malloc 问题比我的具体示例更感兴趣,无论如何我可能会用内存映射文件替换它。失败的 malloc() 比其他任何事情都更像是一个谜……渴望了解您的工具而不是对基本原理感到惊讶。

【问题讨论】:

您是否检查了 GetLastError() 和 errno 以获取更多信息? @blank:将其发布为答案,我会投票给你! 【参考方案1】:

我发现这个问题很有趣,所以我尝试从理论上的 POV 进行研究:

在 64 位中(实际上 48 位可用,由于芯片限制,而更少(44 位?)由于操作系统限制)您当然应该受到虚拟内存碎片的限制,即缺乏连续的虚拟地址空间。原因是虚拟地址空间太大,用尽它是不切实际的。

此外,我们可以预期 物理 内存碎片不应该成为问题,因为虚拟内存意味着不需要连续的物理内存地址范围来满足分配请求。相反,它可以满足任何足够大的内存页集。

所以你一定遇到了别的东西:o.e.其他一些适用于虚拟内存的限制。

Windows 上肯定存在的另一个限制是提交限制。更多信息:

http://blogs.technet.com/b/markrussinovich/archive/2008/11/17/3155406.aspx

可能存在其他可能的限制,例如实际实现如何与实际硬件一起工作的怪癖。想象一下,当尝试创建虚拟地址空间到物理地址空间的映射时,您用完了页表中的条目来进行虚拟地址映射......操作系统内存分配器代码是否关心处理这种不太可能的情况?也许不是……

您可以在此处阅读有关页表实际如何进行虚拟地址转换的更多信息:

http://en.wikipedia.org/wiki/Memory_management_unit

【讨论】:

【参考方案2】:

问题是Visual Studio在编译64位应用程序时并没有定义WIN64,它通常仍然保留WIN32,这对于64位应用程序是错误的。这会导致运行时在定义 _HEAP_MAXREQ 时使用 32 位值,因此所有大的 malloc() 都将失败。如果您将项目(在项目属性下,预处理定义)更改为 WIN64,那么非常大的malloc() 应该没有问题。

【讨论】:

【参考方案3】:

这是一个官方消息来源,声明堆的最大请求大小由您链接的 CRT 库定义(除了您之前的代码整数溢出变为 0,这就是您没有返回 NULL 的原因)(_HEAP_MAXREQ)。

http://msdn.microsoft.com/en-us/library/6ewkz86d.aspx

查看我对大窗口分配的回答 here,我参考了一篇关于 Vista/2008 内存模型改进的 MS 论文。

简而言之,库存 CRT 不支持,即使对于本机 64 位进程,任何大于 4gb 的堆大小也是如此。您必须使用 VirtualAlloc* 或 CreateFileMapping 或其他一些类似物。

哦,我还注意到您声称您的较大分配实际上是成功的,这实际上是不正确的,您误解了 malloc(0x200000000); (十六进制为 8gb),正在发生的事情是由于测试工具的强制转换或其他一些影响,您正在请求 0 字节分配,您绝对没有观察到任何大于 0xfffff000 字节堆的东西正在提交,它是只是你看到整数溢出向下转换。

对 WYSE 的忠告 或 * 保持头脑清醒的小窍门*

唯一使用 MALLOC(或任何其他动态请求)分配内存的方法

void *foo = malloc(SIZE);

决不能(我不能强调)在请求的“()”括号内计算动态内存请求的值

mytype *foo = (mytype *) malloc(sizeof(mytype) * 2);

危险在于会发生整数溢出。

在调用时执行算术总是一个编码ERROR,您必须始终计算数据的TOTAL SUM以在评估请求的语句之前请求。

为什么这么糟糕?我们知道这是一个错误,因为请求是针对动态资源提出的,必须在未来会有一个我们将使用 这个资源。

要使用我们要求的内容,我们必须知道它有多大? (例如数组计数、类型大小等)。

这意味着,如果我们在资源请求的 () 中看到任何算术运算,这是一个错误,因为我们必须复制该代码再次以便适当地使用该数据。

【讨论】:

-1。错了两点。 (1) 您声称在 Windows 上使用 CRT 分配的内存不能超过 4GB。不在Win32上,但你可以在Win64上。 VS2008 中的一个简单实验证实了这一点。我花了 5 分钟来检查(编写程序、编译、进入 CRT 以检查值并观察内部实现的作用)。 (2) 你声称他的大小正在四舍五入,而实际上并非如此。 malloc 的输入值(大小)指定为 size_t 而不是 int。因此,在 64 位 Windows 上,您可以指定任何有效的 64 位大小。 8GB 或 20GB 都在这个范围内。 Stephen:粘贴你损坏的测试用例。我会告诉你哪里出错了。 在应该分配 10 GB 的计算中不会发生整数溢出,除非 size_t 是 32 位 - 在这种情况下,无论如何您都不能分配 10 GB。调用本身之外的计算无济于事。 在分配结构时,始终计算分配大小的观点并不完全正确。 mytype* foo = malloc(sizeof(*foo)) 完全可以,并且将指针传递给周围的结构非常普遍。但是,对于分配的各种缓冲区,原始点是非常正确的。【参考方案4】:

您是否尝试过直接使用VirtualAlloc()VirtualFree()?这可能有助于隔离问题。

您将绕过 C 运行时堆和 NT 堆。 您可以保留虚拟地址空间然后提交。这会告诉你哪个操作失败了。

如果虚拟地址空间保留失败(即使它不应该,根据您所说的判断),Sysinternals VMMap 可能有助于解释原因。开启“显示空闲区域”可以查看空闲虚拟地址空间是如何分片的。

【讨论】:

【参考方案5】:

很可能是碎片化。为简单起见,我们举个例子。

内存由一个 12kb 模块组成。该内存在 MMU 中被组织成 1kb 的块。所以,你有 12 x 1kb 块。您的操作系统使用 100 字节,但这基本上是管理页表的代码。所以,你不能换掉它。然后,您的应用每个都使用 100 字节。

现在,只要您的操作系统和应用程序在运行(200 字节),您就已经使用了 200 字节的内存(占用 2kb 块)。正好为malloc() 留下10kb 可用空间。

现在,您从malloc() 几个缓冲区开始 - A(900 字节)、B(200 字节)。然后,您释放 A。现在,您有 9.8kb 可用空间(非连续)。所以,你尝试malloc()C (9kb)。突然,你失败了。

您在尾部有 8.9k 连续,在前端有 0.9k。您无法将第一个块重新映射到末尾,因为 B 延伸到前 1k 和第二个 1k 块。

您仍然可以malloc() 单个 8kb 块。

当然,这个例子有点做作,但希望对你有所帮助。

【讨论】:

这不是由于 48 位地址空间中的碎片。 10GB 【参考方案6】:

但是为什么 malloc 会失败呢?一世 想不出其他原因

正如之前多次暗示的那样,因为内存碎片

【讨论】:

【参考方案7】:

这里只是一个猜测,但 malloc 分配连续内存,您的堆上可能没有足够大的连续部分。以下是我会尝试的一些事情;

一个 20GB 的 malloc 失败了,四个 5GB 的 malloc 成功了吗?如果是这样,这是一个连续的空间问题。

您是否检查过编译器开关是否限制了总堆大小或最大堆块大小?

您是否尝试过编写一个声明所需大小的静态变量的程序?如果这可行,您可以在该空间中使用大型 malloc 实现您自己的堆。

【讨论】:

我不买它——有一个 64 位的虚拟地址空间。我看不出堆如何找到 10GB 连续的。 可能不会,但如果我们相信 OP 发布的内容确实会发生。如果您可以在较小的块中分配相同数量的内存,则可能是操作系统无法提供跨越实际内存和交换空间的大堆块。 您的虚拟内存不受 64 位地址空间的限制。它受到主内存和交换文件 AFAIK 大小的限制。【参考方案8】:

malloc 尝试分配一个连续的内存范围,这最初将在实际内存中,这仅仅是因为交换内存的工作方式(至少据我所知)。您的操作系统有时可能无法找到一个连续的 10gb 内存块,并且仍然将所有需要实际内存的进程同时留在 RAM 中(此时您的 malloc 将失败)。

您是否真的需要 10gb 的连续内存,或者您是否能够将存储类/结构包装在几个较小的块周围并以块的形式使用您的内存?这放宽了巨大的连续要求,并且还应该允许您的程序将交换文件用于较少使用的块。

【讨论】:

在几分之一秒内击败我 ;) 将 malloc 分解为更小的部分是非常正确的,10GB 比当前的主流 PC 稍早。 blank 是绝对正确的.. 64 位的地址空间比仅仅 10 GB 大得多! (请注意,可能存在隐藏的 2^48 地址大小限制,但仍远大于 10GB !=2^33) 地址 space 不会有任何问题。但是可用内存不一定会填满地址空间。 AFAIK,内存最初不能在虚拟内存中分配,所以如果没有办法将物理内存分页以获得 10gb 的连续块,malloc 将失败。即使不是这种情况,也只有 12gb 和 32gb 的可寻址内存,而不是完整的 64 位可用空间。 @work,有没有提到 malloc 需要小于当前未使用的物理内存大小的限制?听起来确实可以回答这个问题,但我不明白为什么会存在这样的限制。如果您的操作系统碰巧保留了很多一次性文件缓存,那不会杀死许多常规(合理)的 malloc(例如 100MB)吗? 对于 linux,在使用巨大的 malloc 时必须小心内存过度使用 [linux-mm.org/OverCommitAccounting]。【参考方案9】:

您是否尝试过使用heap functions 来分配内存?

【讨论】:

以上是关于64 位大型 malloc的主要内容,如果未能解决你的问题,请参考以下文章

使用 Pandas 读取大型文本文件 [重复]

64位系统与32位系统区别

IDEA 调整 VM 配置文件(64位)

如何在 64 位 JVM 上运行 TeamCity

尝试使用 64 位 MFC DLL 的 C++ 32 位应用程序

NSDecimalNumber 和大的无符号长长(64 位)整数