【问题标题】:Read 4,000,000,000 lines from a file and save into an array in C从文件中读取 4,000,000,000 行并保存到 C 中的数组中
【发布时间】:2016-07-19 14:23:36
【问题描述】:

我需要从一个文件中读取 4,000,000,000 行并将它们保存到一个数组中。

但是Linux内核因为内存不足而杀死了进程:

tail /var/log/kern.log
... Out of memory: Kill process ...

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


int main() {

    /*
     * Read line by line from the file and write into the array
     */ 

    int lines_allocated = 128;
    int max_line_len = 15;

    char **array = (char **)malloc(sizeof(char*)*lines_allocated);
    if (array==NULL) {
        fprintf(stderr,"Out of memory (1).\n");
        exit(1);
    }

    file = fopen("file", "r");
    if (file == NULL) {
        fprintf(stderr,"Error opening file.\n");
        exit(2);
    }

    int il;
    for (il=0;1;il++) {
        int j;

        /* Have we gone over our line allocation? */
        if (il >= lines_allocated) {
            int new_size;

            /* Double our allocation and re-allocate */
            new_size = lines_allocated*2;
            array = (char **)realloc(array,sizeof(char*)*new_size);
            if (array==NULL){
                fprintf(stderr,"Out of memory.\n");
                exit(3);
            }
            lines_allocated = new_size;
        }

        /* Allocate space for the next line */
        array[il] = malloc(max_line_len);
        if (array[il]==NULL)
            {
            fprintf(stderr,"Out of memory (3).\n");
            exit(4);
            }
        if (fgets(array[il], max_line_len-1, file)==NULL)
            break;

        /* Get rid of CR or LF at end of line */
        for (j=strlen(array[il])-1;j>=0 && (array[il][j]=='\n' || array[il][j]=='\r');j--)
            ;

        array[il][j+1]='\0';
    }

    /* Close login file */
    fclose(file);

    /* Print the array of data from the file */
    for (i=0; i < il; i++)
        printf("%s\n", array[i]);

    return 0;
}

什么是最合适和最有效的方法?也许读取第一个块,完成它,然后读取下一个块等等?

这个问题有什么解决办法?

【问题讨论】:

  • 在较小的行之后阅读它。
  • 如您所说,您可以一次读取 1 行,对其进行处理,然后对剩余的数据行执行相同操作。
  • 你自己回答了。一块一块是正确的方法
  • 您需要同时处理所有的数据吗?还是顺序?如果您可以按顺序处理它,那么一次读取并处理一行。否则分块读取。或者你可以memory map the whole or part of the file
  • “最合适的方式”取决于您将如何处理这些数据。给我们更多的背景信息。

标签: c linux memory-management out-of-memory


【解决方案1】:

假设您从每一行读取一个byte,您将需要 29GB 内存。

对于如此庞大的数据,至关重要的是加载尽可能少的数据,然后在处理完毕后释放内存。否则你会错过记忆。

【讨论】:

  • 为什么要“尽可能少的数据”?我个人会得到一个很好的、固定大小的缓冲区(但不是太胖)并以大块的形式读取(但不是千兆字节大小)。然后我会从那个胖缓冲区中逐行读取。无需每次都 malloc 和 free。
  • 内存映射文件呢?
【解决方案2】:

显而易见的解决方案是以块的形式处理输入。如果你能做到这一点,这有一个额外的好处,你可以在 I/O 进行时(在当前块上)进行计算(大多数操作系统都进行前瞻缓冲),与第一次读取所有数据相比,可以缩短实际时间,然后在一个块中处理它。

(对于那些正在实现sort -type 实用程序的人,我经常提到这一事实。对于真正的、人类可测量的加速,您应该将输入读入树中,而不是读入数组,然后对数组进行排序,因为存储速度很慢,并且将每个输入记录(行)立即插入树中,本质上可以让您使用等待 I/O 对数据进行排序所花费的时间。它可能会使用更多的 CPU 时间(取决于您的树实现)与非常好的数组排序算法相比,但人类通常不太关心这一点。我们主要关心现实世界中的等待时间。我们不喜欢等待。)


如果输入记录(行)是随机访问的,并且您在 64 位架构上工作,那么内存映射技术可能会成为可能。 (如果您可以分块完成工作,那么就这样做;如果您不能,即使您没有足够的 RAM,内存映射也可以让您进行处理。)

几年前,我向manipulate a terabyte data structure 写了一个关于如何在 64 位 Linux 中使用文件支持的稀疏内存映射的最小示例。

在这种情况下,您可以使用只读内存映射来随机访问文本文件。请注意,在 Linux 中,您应该使用mmap(NULL, aligned_length, PROT_READ, MAP_SHARED | MAP_NORESERVE, fd, 0),其中aligned_length 是文本文件的长度,四舍五入到sysconf(SC_PAGESIZE) 的下一个倍数。这样的映射不使用交换(因此交换空间的数量不会限制映射的大小),但是如果内存太紧,内核可以并且会丢弃页面(并在需要时从文件本身重新读取)。

第二个内存映射可用于将偏移量(在 64 位架构上,size_t 足够大)存储到每行的开头。此映射应使用mmap(NULL, offbufsize, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd2, 0),其中offbufsizesysconf(_SC_PAGESIZE) 的倍数,但至少与输入文件中的行数乘以sizeof (size_t) 一样大。

确定每一行的开头是一个最好在块中完成的操作(比如 512×1024 = 524288 字节)。也就是说,你应该先设置第二个映射,必要时使用ftruncate()mremap()来增长它,在上述块中使用低级I/O(unistd.h read())读取第一个文件以确定每行的开头。

我个人在实践中做过类似的事情。即使有通用换行支持(同时自动支持\r\n 或 CRLF、\n\r 或 LFCR、\r 或 CR,以及 \n 或 LF),您也只需要在用于存储从大文本文件中读取的块的数组。由于初始传递是完全线性的,因此可能需要使用posix_fadvise(fd,0,0,POSIX_FADV_NOREUSE)posix_fadvise(fd,0,0,POSIX_FADV_SEQUENTIAL)

鉴于至少有几千兆字节的 RAM 可用于缓冲(即使在小型 64 位 Linux/POSIX 工作站上这也被认为是一种典型情况),即使 Linux 内核分页的东西并不“神奇”,它的工作原理也令人惊讶嗯。

我在 Linux 中看到的比可用 RAM 映射大得多的性能不令人满意的最常见原因是忘记了 MAP_NORESERVE 标志。这会导致不必要的限制(可用交换空间限制了允许的最大映射大小),以及在内存紧张时性能不佳(因为页面写入交换,而不是直接掉到地板上)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-20
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    相关资源
    最近更新 更多