【问题标题】:Reserved Memory Equals Shared Memory but Memory is Never Reserved保留内存等于共享内存,但从不保留内存
【发布时间】:2021-06-05 22:13:43
【问题描述】:

我目前正在编辑我继承的程序,以便能够处理 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", &amp;fileIT); 被多次调用(对于 6 个数组中的每一个),内存似乎也会显着增加

【问题讨论】:

  • 有什么问题?什么是RSS? (也许您正在查看错误的指标)
  • 感谢您的评论!立即检查
  • 不确定我是否正确,但我跑了cat /proc/8904/status,其中 8904 是 pid,它说RssFile: 36050208 kB 所以看起来差不多.. RssAnon 是 104 kb 和 RssShmem是 0kb
  • 用较小的阵列测试(或检查系统日志)也许你遇到了OOM杀手。
  • 抱歉,有点草率。我一直在想其他的事情要说。我现在完成了:-)

标签: c memory-management shared-memory mmap


【解决方案1】:

“Res”是“resident”的缩写,而不是“reserved”。常驻内存是指此时内核恰好驻留的进程内存;虚拟内存系统可能会在任何时候删除一个常驻页面,因此它不是任何限制。但是,内核尝试不交换似乎处于活动状态的页面。如果您的进程在内存中搅动太多页面,OOM 杀手将采取行动。如果您按顺序使用数据,那么您映射了多少通常并不重要,因为只有最近的页面才会常驻。但如果你在记忆中跳跃,在这里读一点,在那里写一点,那么你会造成更多的流失。这似乎是正在发生的事情。

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

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

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

需要注意的一点:下面的 sn-p 并没有按照评论所说的那样做:

    // 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 进行映射。 PROT_READ 页面对内核来说更容易处理,因为它们可以被删除并稍后再读回。 (对于可写页面,内核会跟踪页面已被修改的事实,但如果您不打算编写并且不允许修改,那么内核的任务会更容易一些。)

【讨论】:

  • 谢谢!我会确保解决这个问题,这很有意义。但是,我想当我分配更多内存(稍后为 50 x 2 矩阵)时,mmaped 内存开始被换出是不是这样?情况似乎并非如此,但我可能遗漏了一些东西。
  • @HonoredTarget:是的,内存将被换出。它正在被换出(或不被换入),因为您的常驻集远小于文件的总大小。但是,如果您的访问模式确实跳过了很多(或者非常快速地一遍又一遍地顺序读取),那么磁盘可能跟不上。如果您正在修改文件,则尤其如此,因为在回收内核缓冲区之前必须写出已修改的页面,这非常耗时。
  • 啊。我更新了我只阅读的文件(旁注:你知道 memcpy 是否可以与 mmap 一起使用吗?)。但是,我想我不知道这是否足以让它杀死它?更有意义的是为什么内存在增加。
  • @HonoredTarget:另一件事是内核不会在不需要时交换页面,即使该页面已经很长时间没有被引用。如果您的机器上没有其他进程正在尝试使用内存,那么驻留集将增长到填满您的大部分 RAM。如果最近没有使用大部分内存,那么一旦其他进程尝试工作,驻留集就会迅速缩小。
  • @honoredTarget:我不知道。您必须查看 OOM 日志,但它们并不容易解析。一种可能是您的内存碎片化了(可能是通过进行大量不连续的修改),然后您要求一个需要扩展页表的内存块。由于页表在实际内存中必须是连续的,内存碎片可能会导致无法分配更大的页表,这将触发 OOM 杀手。
【解决方案2】:

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

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

【讨论】:

    猜你喜欢
    • 2015-04-28
    • 1970-01-01
    • 1970-01-01
    • 2021-10-17
    • 2019-01-01
    • 2011-05-13
    • 2011-01-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多