保留内存等于共享内存,但内存从不保留

Reserved Memory Equals Shared Memory but Memory is Never Reserved

我目前正在编辑我继承的能够处理 23 GB 文件的程序。因此,为了保持低内存,我使用 mmap 来加载我在之前的程序中创建的数组。然而,我加载了这些数组,然后进入一个函数,共享和保留内存激增,尽管我不相信我曾经分配过任何东西。当 运行 时,内存从 0 开始,然后迅速增加到 90%(~36GB,因为我有 40GB 的内存)并保持在那里。最终,我开始需要内存(小于 30GB)然后程序被杀死。

通常,我会怀疑这个问题是由于分配引起的,或者我以某种方式分配了内存。但是,我没有分配任何内存(尽管我正在阅读 mmaped 文件)。

奇怪的是,保留的内存等于共享的内存量(见附件截图)。

我编写的用于访问映射数组的函数:

double* loadArrayDouble(ssize_t size, char* backupFile, int *filedestination) {
    *filedestination = open(backupFile, O_RDWR | O_CREAT, 0644);
    if (*filedestination < 0) {
        perror("open failed");
        exit(1);
    }
    // make sure file is big enough
    if (lseek(*filedestination,size*sizeof(double), SEEK_SET) == -1) {
        perror("seek to len failed");
        exit(1);
    }
    
    if (lseek(*filedestination, 0, SEEK_SET) == -1) {
        perror("seek to 0 failed");
        exit(1);
    }

    double *array1 = mmap(NULL, size*sizeof(double), PROT_READ | PROT_WRITE, MAP_SHARED, *filedestination, 0);
    if (array1 == MAP_FAILED) {
        perror("mmap failed");
        exit(1);
    }

    return array1;
}

如果有任何其他代码要包含,请告诉我。即使多次调用 double* file1 = loadArrayDouble(SeqSize, "/home/HonoredTarget/file1", &fileIT);(对于 6 个数组中的每一个),内存似乎也会显着增加

“Res”是“resident”的缩写,不是“reserved”。驻留内存是指此时内核刚好驻留的进程内存;虚拟内存系统可能会随时丢弃一个驻留页面,所以这绝不是一个限制。但是,内核会尝试不换出看似活动的页面。如果您的进程在内存中搅动太多页面,OOM 杀手就会起作用。如果您按顺序使用数据,那么您映射了多少通常并不重要,因为只有最近的页面才会驻留。但是如果你在记忆中跳来跳去,在这里读一点,在那里写一点,那么你就会产生更多的流失。这似乎是正在发生的事情。

“shr”(共享)内存实际上指的是可以与另一个进程共享的内存(无论它实际上是否与另一个进程共享)。您使用 MAP_SHARED 的事实意味着所有 mmap 页面都被共享也就不足为奇了。如果您的程序修改了文件中的数据,您需要 MAP_SHARED,我猜它确实如此。

“virt”(虚拟)列衡量您实际映射了多少地址空间(包括通过您使用的任何动态分配库映射到匿名后备存储的内存)。170G 似乎有点高我。如果同时映射了六个 23GB 的文件,那就是 138GB。但也许这些数字只是估计。无论如何,只要您在您设置的虚拟内存限制内,这并不重要。 (虽然页表确实占用实内存,所以还是有一定作用的。)

内存映射并不能节省你的内存,真的。当你 mmap 一个文件时,文件的内容仍然需要被读入内存,以便你的程序使用这些数据。 mmap 的一大优势是您不必费力地分配缓冲区和发出读取调用。此外,无需从读取文件的内核缓冲区复制数据。所以它可以更容易和更有效,但并非总是如此;这在很大程度上取决于精确的访问模式。

需要注意的一件事:以下代码片段与评论所说的不符:

    // make sure file is big enough
    if (lseek(*filedestination,size*sizeof(double), SEEK_SET) == -1) {
        perror("seek to len failed");
        exit(1);
    }

lseek只是设置下一次读写操作的文件位置。如果文件没有扩展到那个点,你会在阅读时得到一个 EOF 指示,或者如果你写,文件将被(稀疏地)扩展。所以真的没有太大意义。如果要检查文件大小,请使用 stat。或者确保在执行搜索后至少读取一个字节。

open 调用中使用 O_CREAT 也没有太多意义,因为如果文件不存在并因此被创建,它的大小将为 0,这大概是一个错误。关闭 O_CREAT 意味着如果文件不存在,open 调用将失败,这可能是您想要的。

最后,如果您实际上没有修改文件内容,请不要使用 PROT_WRITE 进行 mmap。 PROT_READ 页面对于内核来说更容易处理,因为它们可以被删除并稍后读回。 (对于可写页面,内核会跟踪页面已被修改的事实,但如果您不打算写入并且不允许修改,那么内核的任务会更容易一些。)

因为你(显然)让你的进程被 OOM 杀手杀死,即使你使用的内存是 MAP_SHARED(所以永远不需要后备存储——它由文件自动支持),看起来你是 运行 你的 linux 没有交换 space,如果你有像这样的大映射文件,这是一个坏主意,因为它会导致进程在你的居民时被杀死内存接近您的物理内存。因此,显而易见的解决方案是添加一个交换文件——即使是少量 (1-2GB) 也可以避免 OOM 杀手问题。网上有很多关于如何在 linux 中添加交换文件的教程。你可以看看here or here或自己搜索

如果出于某种原因您不想添加交换文件,您可以通过增加系统的“swappiness”来降低被杀死的频率——这将导致内核丢弃页面您的映射文件更容易,从而减少进入 OOM 情况的可能性。您可以通过在 sysctl.conf 文件(用于启动)中增加 vm.swappiness 参数或将新值写入 /proc/sys/vm/swappiness 文件来实现此目的。