代码之家  ›  专栏  ›  技术社区  ›  Rohit Pandey

用C语言高效读取扁平文件

  •  4
  • Rohit Pandey  · 技术社区  · 6 年前

    我正在尝试读取一个大文件,它在C语言中每行有一个浮点数。为此,我将下面的代码放在一起。它在测试小数据时工作良好。然而,以这种方式读取6亿个数字时,速度非常慢。有什么办法可以加快速度吗?我是通过python生成原始文件的,所以重新格式化数据(例如在一行中用逗号分隔多个数字)也是一个选项。任何关于这种方法为何如此缓慢的洞察都将受到极大的赞赏。

    void read_file(float *W)
    {
       FILE *fp;
    
       int i = 0;
    
    // In this file, one row should contain only one NUMBER!!
    // So flatten the matrix.
       if (fp = fopen("C:\\Users\\rohit\\Documents\\GitHub\\base\\numerical\\c\\ReadFile1\\Debug\\data.txt", "r")) {
          while (fscanf(fp, "%f", &W[i]) != EOF) {
             ++i;
          }
          fclose(fp);
       }
    
       fclose(fp);
    
       scanf("%d",&i);    
    }
    
    2 回复  |  直到 6 年前
        1
  •  4
  •   Joseph Quinsey Taseen    6 年前

    几年前我也遇到过类似的问题。解决办法是 fscanf 具有 fgets strtod . 如果我没记错的话,这给了我超过10倍的提高。

    所以你的循环:

      while (fscanf(fp, "%f", &W[i]) != EOF) {
         ++i;
      }
    

    应该看起来像:

      while (fgets(buf, sizeof buf, fp)) {
         W[i++] = strtod(buf, 0);
      }
    

    编辑: 错误检查总是一个好主意。加上这个,简单的两行就变成了10行:

      char buf[80];
      errno = 0;
      while (!errno && fgets(buf, sizeof buf, fp)) {
          W[i++] = strtod(buf, 0);
      }
      if (errno) { // Maybe ERANGE or EINVAL from strtod, or a read error like EINTR
          int save = errno;
          printf("errno=%d reading line %d\n", save, i); // or perror()
          exit(1);
      }
    

    编辑2: 关于错误检查,输入文件可以很容易地包含如下文本 nan inf ,可能是来自上游的某个bug。但是 字符串转换为浮点数 fscanf 非常乐意分析这些。这可能会导致 你的 代码。

    但它很容易检查。添加代码:

      int bad = 0;
      for (int j = 0; j < i; j++)
          bad += !isnormal(W[j]); // check for nan, inf, etc.
      if (bad) {
         // ... handle error
      }
    

    把它放在一个单独的、简单的循环中可以使编译器更容易地进行优化(理论上),特别是当您使用 #pragma GCC optimize ("unroll-loops") .

        2
  •  4
  •   Joseph Quinsey Taseen    6 年前

    (评论:这是我的第二个回答。)我在评论中看到一篇评论:

    你是否碰巧有一个C语言的样本来读取二进制浮点数?

    二进制版本会将任何ascii版本从水中吹出。而且更短。

    这里,OP的函数签名已更改为包含返回中的最大浮动数。 W ,并返回从文件中实际读取的数字。

    size_t read_file(float *W, size_t maxlen)
    {
        FILE *fp = fopen("C:\\Users\\rohit\\Documents\\GitHub\\base\\numerical\\c\\ReadFile1\\Debug\\data.txt", "r");
        return fp ? fread(W, sizeof(float), maxlen, fp) : 0;
    }
    

    或者为了什么 即使 快点,你可以用 mmap ... 。但这在Windows上不可用。


    补充: 然而,无缓冲I/O可能更快。以下函数使用一个 malloc 一个没有缓冲的 read 将文件复制到堆中。(注:尚未在大文件上测试;可能需要 open64 .)

    #include <stdlib.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>  
    #include <sys/stat.h>  
    
    void *readFileToHeap(const char *file, int *len) {
       void *retval = 0;
       ssize_t cnt;
       struct stat st;
       const int fd = open(file, O_RDONLY, 0);
       if (fd < 0)
          return printf("Cannot open %s\n", file), (void *)0;
       if (fstat(fd, &st))
          return perror("fstat()"), close(fd), (void *)0;
       if (!(retval = malloc(st.st_size)))
          return perror("malloc()"), close(fd), (void *)0;
       cnt = read(fd, retval, st.st_size);
       close(fd); // not the best: could clobber errno
       if (cnt < 0)
          return perror("read()"), free(retval), (void *)0;
       if (cnt != st.st_size)
          return printf("Partial read %d\n", cnt), free(retval), (void *)0;
       *len = cnt;
       return retval;
    }