如何使内核压缩碎片内存


8

我在跑步Fedora 26

这是我的算法教授给我的一个非常奇怪的任务。作业说:

C中的内存碎片:
设计,实现和执行一个C程序,该程序执行以下操作:它为3m每个大小为800,000个元素的数组序列分配内存;然后它显式地取消分配所有偶数数组,并分配m每个大小为900,000个元素的数组序列。测量程序分配第一个序列和第二个序列所需的时间。选择m耗尽几乎所有程序可用的主内存。”

这样做的总体目标是对内存进行分段,然后请求的内存量要大于连续块的可用内存量,从而迫使操作系统压缩或整理内存碎片。

在课堂上,我问我们应该怎么做,因为内存是可视的,而不是实际上是连续的,他回答:“好吧,您必须关闭[虚拟内存]。” 其他一些学生在课堂上问我们如何知道何时达到“垃圾收集”状态,他说:“第二次分配的时间应该比第一次分配的时间长,因为垃圾收集需要花费时间”

经过一番搜索后,我发现禁用虚拟内存的最接近方法是使用禁用交换内存swapoff -a。我禁用了桌面环境,并从本机终端编译并运行了程序(以避免受到其他进程的干扰,尤其是诸如桌面环境之类的繁重进程)。我这样做并以递增的方式运行程序,m直到达到第二个分配的时间大于第一个分配的时间。

我以递增的方式运行该程序,m并最终找到了第二个分配的时间比第一个分配的时间长的点。但是,在此过程中,我遇到了一个问题,即在第二次分配之前该进程已被终止。我检查了一下dmesg,发现它被oom-killer 杀死。我发现并阅读了有关oom-killer的几篇文章,发现可以禁用内核对内存的过度分配。

我这样做并再次运行程序,只是这次我找不到m第二个的时间比第一个大的时间。最终,随着m越来越大(尽管比启用了过度分配时要小得多),malloc将失败并且我的程序将终止。

我有三个问题,第一个并不是那么重要:

  1. 垃圾回收是否是正确的术语?我的教授非常坚定地说这是垃圾收集,但是我假设垃圾收集是通过编程语言完成的,因此将其视为更碎片整理。

  2. 在Linux系统上是否可以像他想的那样进行压缩?

  3. 为什么在禁用交换但仍启用了内存过度分配的情况下,第二次分配的时间比第一次分配的时间长?压实实际上发生了吗?如果是这样,为什么在禁用内存过度分配后无法达到压缩的程度?


您不能“关闭”虚拟内存。另外,请注意,在Linux中,您可以在逻辑上分配比实际更多的内存-内核只有在写入页面后才实际分配页面。
安迪道尔顿

3
我还等着写作业!题。但是,这是一个“ 我的作业”。我的作业似乎不明确,思想欠缺并且不可能。是吗?题。其中一些是Stack Overflow领域,您会在stackoverflow.com/questions/4039274(只是随机选择一个示例)中找到很多问答集。
JdeBP

Answers:


5

到目前为止,对您的研究表示赞赏,这确实是一组有趣的问题。

通常,这里需要考虑一个重要方面:内存分配部分是操作系统的责任,部分是每个运行进程的责任(忽略没有内存保护和虚拟地址空间的旧系统)。操作系统负责为每个进程提供其自己的地址空间,并在必要时为进程分配物理内存。每个进程都在一定程度上精心设计地址空间,并确保正确使用它。请注意,由于运行时环境会处理大多数事情,因此程序员的责任在很大程度上对程序员是不可见的。

现在,回答您的问题...

  1. 在我看来,垃圾回收是您在此处所做的一项步骤。我想您正在使用malloc()和编写C语言free()垃圾回收在编程语言运行时环境的支持下处理后半部分:它标识先前分配但不再使用(并且重要的是永远不能使用)的内存块,并返回它们分配器。链接的问题JdeBP意见提供了一些背景,但我觉得它主要是有趣的,因为它表明,不同的人对垃圾收集很不同的意见,甚至什么是垃圾收集。

    在我们感兴趣的情况下,我将使用“内存压缩”来讨论正在讨论的过程。

  2. 从用户空间编程的角度来看,您的教授所要求的在Linux下的C语言中是不可能的,原因很简单:我们关心的不是物理内存碎片,而是地址空间碎片。当分配许多800,000字节的块时,最终将获得与每个块一样多的指针。在Linux上,此时,操作系统本身并没有做很多事情,并且您不一定需要物理内存来支持每个分配(顺便说一句,分配较小的操作系统根本就不会涉及到操作系统。 C库的分配器;但是这里的分配足够大,C库将使用mmap,由内核处理)。释放奇数编号的块时,您会获得地址空间的那些块,但无法更改必须指向其他块的指针。如果随手打印出指针,您会发现它们之间的区别不超过分配请求(我的系统上为802,816字节);对于900,000字节的块,两个指针之间没有空间。因为您的程序具有指向每个块的实际指针,而不是一些抽象值(在其他上下文中为句柄),所以运行时环境对此无能为力,因此无法压缩其内存以合并空闲的块。

    如果您使用的编程语言中的指针不是程序员可见的,那么在Linux下可以进行内存压缩。另一种可能性是在返回的值不是指针的情况下使用内存分配API。例如,请参见Windows下基于句柄的堆分配函数(其中,仅当句柄被锁定时,指针才有效)。

  3. 您教授的练习有效地衡量了的性能mmap,其中包括其自由移动算法。首先分配3× m个块,然后释放其中的一半,然后再次开始分配m个块;释放所有这些块将大量空闲块转储到内核的分配器上,需要跟踪该分配器(并且free调用所花费的时间表明,此时尚未进行优化)。如果您跟踪每个单独块的分配时间,那么您会看到前900k分配花费了很多比其他时间更长(在我的系统上为三个数量级),第二个要快得多,但仍要花费更长的时间(两个数量级),第三个分配又回到了典型的性能水平。因此,发生了一些事情,但是返回的指针表明这不是内存压缩,至少不是分配的块压缩(如上所述,这是不可能的)—大概时间对应于处理内核用来处理数据结构的时间。跟踪进程中的可用地址空间(我正在检查这一点,稍后将进行更新)。这些冗长的分配可能会使您正在测量的总体分配序列相形见,,此时900k分配的总体占用时间比800k分配的更长。

    过量使用会更改您看到的行为的原因是,它将更改操作从纯粹操纵地址空间更改为实际分配内存,从而减小了操场的大小。当您过量使用时,内核仅受进程的地址空间限制,因此您可以分配更多的块,并给分配器施加更多的压力。当您禁用过量使用时,内核将受到可用内存的限制,从而将您可以拥有的值m降低到分配器压力不足以至于分配时间用完的水平。


在这里使用calloc()或实际写入分配的数组会有所不同吗?
库沙兰丹

写入分配的内存将取消过量使用功能,但是很难处理,因为失败会导致OOM杀手介入(并不一定会杀死过度分配过程)。calloc()具有较大分配的行为与malloc()在Linux上的行为相同,mmap()用于分配匿名映射,该映射在首次使用时为零填充(因此过量使用仍然有效)。
斯蒂芬·基特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.