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

时间:2021-03-08 00:21:32

标签: c memory-management shared-memory mmap

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

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

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

Output

我为访问 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 个数组中的每一个),内存似乎也显着增加了

2 个答案:

答案 0 :(得分:1)

“Res”是“resident”的缩写,不是“reserved”。常驻内存是指内核恰好驻留在此刻的进程内存;虚拟内存系统可能会随时删除常驻页面,因此这绝不是限制。但是,内核试图不换出似乎处于活动状态的页面。如果您的进程在内存中搅动了太多页面,OOM 杀手就会起作用。如果您按顺序使用数据,那么您已经进行了多少 mmap'ed 通常并不重要,因为只有最近的页面才会被驻留。但是如果你在内存中跳过,在这里读一点,在那里写一点,那么你会创造更多的流失。这似乎正在发生。

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

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

内存映射并不能节省您的内存,真的。当你映射一个文件时,文件的内容仍然需要被读入内存以便你的程序使用这些数据。 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。或者确保在执行搜索后至少读取一个字节。

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

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

答案 1 :(得分:1)

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

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

相关问题