【问题标题】:What is the fastest way to read in a large data file of text columns?读取文本列的大型数据文件的最快方法是什么?
【发布时间】:2010-09-23 13:53:39
【问题描述】:

我有一个将近 900 万行的数据文件(很快就会超过 5 亿行),我正在寻找读取它的最快方法。五个对齐的列被填充并用空格分隔,所以我知道在每行的哪个位置查找我想要的两个字段。 我的 Python 例程需要 45 秒:

import sys,time

start = time.time()
filename = 'test.txt'    # space-delimited, aligned columns
trans=[]
numax=0
for line in open(linefile,'r'):
    nu=float(line[-23:-11]); S=float(line[-10:-1])
    if nu>numax: numax=nu
    trans.append((nu,S))
end=time.time()
print len(trans),'transitions read in %.1f secs' % (end-start)
print 'numax =',numax

而我在 C 中提出的例程是更令人愉悦的 4 秒:

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

#define BPL 47
#define FILENAME "test.txt"
#define NTRANS 8858226

int main(void) {
  size_t num;
  unsigned long i;
  char buf[BPL];
  char* sp;
  double *nu, *S;
  double numax;
  FILE *fp;
  time_t start,end;

  nu = (double *)malloc(NTRANS * sizeof(double));
  S = (double *)malloc(NTRANS * sizeof(double));

  start = time(NULL);
  if ((fp=fopen(FILENAME,"rb"))!=NULL) {
    i=0;
    numax=0.;
    do {
      if (i==NTRANS) {break;}
      num = fread(buf, 1, BPL, fp);
      buf[BPL-1]='\0';
      sp = &buf[BPL-10]; S[i] = atof(sp);
      buf[BPL-11]='\0';
      sp = &buf[BPL-23]; nu[i] = atof(sp);
      if (nu[i]>numax) {numax=nu[i];}
      ++i;
    } while (num == BPL);
    fclose(fp);
    end = time(NULL);
    fprintf(stdout, "%d lines read; numax = %12.6f\n", (int)i, numax);
    fprintf(stdout, "that took %.1f secs\n", difftime(end,start));
  } else {
    fprintf(stderr, "Error opening file %s\n", FILENAME);
    free(nu); free(S);
    return EXIT_FAILURE;
  }

  free(nu); free(S);
  return EXIT_SUCCESS;
  }

Fortran、C++ 和 Java 中的解决方案需要中等量的时间(27 秒、20 秒、8 秒)。 我的问题是:我在上面是否犯了任何令人发指的错误(特别是 C 代码)?有什么方法可以加快 Python 例程的速度吗?我很快意识到将我的数据存储在一个元组数组中比为每个条目实例化一个类要好。

【问题讨论】:

  • 请解释一下你的 C 代码中的神奇数字(你从哪里得出 47 和 858226?)
  • 你应该在你的 python 代码上运行分析器,看看它在哪里慢。此外,您应该尝试遵循 python 的 pep8 样式约定,它们使其更易于阅读。
  • sorry - BPL=47 是每行的字节数,包括 \n EOL 字符; 8588226 是文件中的总行数 - 所以我知道存储数据需要多少内存。
  • 在python方面,我想你可以通过返回一个迭代器而不是构建一个数组来显着加快它。

标签: python c io dataset


【解决方案1】:

几点:

  1. 您的 C 程序作弊;它被告知文件大小,并且正在预分配......

  2. Python:考虑使用array.array('d') ... S 和 nu 各一个。然后尝试预分配。

  3. Python:将例程编写为函数并调用它——访问函数局部变量比访问模块全局变量要快。

【讨论】:

  • 谢谢 - 我认为你的观点 2. 是为什么读取预分配的 numpy 数组让我的时间缩短到 20 秒。将其作为函数调用,我们将缩短到 17.3 秒。谁能指点一下如何实现迭代器方法?
【解决方案2】:

一种可能适用于 C、C++ 和 python 版本的方法是使用内存映射文件。最显着的好处是它可以减少数据从一个缓冲区复制到另一个缓冲区时的双重处理量。在许多情况下,由于 I/O 系统调用次数的减少,也有好处。

【讨论】:

  • +1;内存映射可以在 Java 和 Fortran 中通过 C 绑定本地完成......
【解决方案3】:

在 C 实现中,您可以尝试将 fopen()/fread()/fclose() 库函数替换为较低级别的系统调用 open()/read()/close()。加速可能来自以下事实:fread() 做了很多缓冲,而read() 没有。

此外,使用更大的块减少调用read() 将减少系统调用的数量,因此您将在用户空间和内核空间之间进行更少的切换。当您发出read() 系统调用(不管它是否从fread() 库函数调用)时,内核所做的就是从磁盘读取数据,然后将其复制到用户空间。如果您在代码中经常发出系统调用,则复制部分会变得很昂贵。通过读取更大的块,您最终会减少上下文切换和复制。

请记住,虽然 read() 不能保证返回您想要的确切字节数的块。这就是为什么在可靠且正确的实现中,您始终必须检查 read() 的返回值。

【讨论】:

  • 我不明白。首先,您建议不要通过将 fread()/... 函数更改为 read()/... 函数来使用缓冲。然后你建议通过读取更大的数据块来使用缓冲。恕我直言,您建议避免自动缓冲,并实际使用手动缓冲来解决错误实现中的所有可能错误。
  • 正确的实现将始终检查read() 实际读取的字节数。关键是使用 47 字节内存块调用 read(2) 的效率远低于使用 1024 字节块调用的效率。
  • 当然,但这正是您使用fread() 的原因——它会以更大的值调用read() 并为您进行缓冲。
【解决方案4】:

您在fread() 中有错误的1BPL 参数(按照您的方式,它可以读取部分行,您不对其进行测试)。您还应该在尝试使用返回的数据之前测试fread() 的返回值

您可以通过一次读取多行来加快 C 版本的速度

#define LINES_PER_READ 1000
char buf[LINES_PER_READ][BPL];

/* ... */

   while (i < NTRANS && (num = fread(buf, BPL, LINES_PER_READ, fp)) > 0) {
      int line;

      for (line = 0; i < NTRANS && line < num; line++)
      {
          buf[line][BPL-1]='\0';
          sp = &buf[line][BPL-10]; S[i] = atof(sp);
          buf[line][BPL-11]='\0';
          sp = &buf[line][BPL-23]; nu[i] = atof(sp);
          if (nu[i]>numax) {numax=nu[i];}
          ++i;
      }
    }

在支持posix_fadvise() 的系统上,您也应该在打开文件后预先执行此操作:

posix_fadvise(fileno(fp), 0, 0, POSIX_FADV_SEQUENTIAL);

【讨论】:

    【解决方案5】:

    考虑到您需要执行此操作的次数,另一种可能的加速方法是使用指向 S 和 nu 的指针而不是索引到数组中,例如,

    double *pS = S, *pnu = nu;
    ...
    *pS++ = atof(sp);
    *pnu = atof(sp);
    ...
    

    此外,由于您总是在 buf 中的相同位置从 char 转换为 double,因此请预先计算循环之外的地址,而不是每次在循环中计算它们。

    【讨论】:

    • 请在不这样做的情况下进行测量。它无助于提高可读性,而且通常对性能没有任何影响。
    • 感谢您的回答 - 我在执行此操作时遇到分段错误?另外,如果 BPL 是#defined,编译器不会在执行之前为我做这个预计算吗?
    • 我认为大多数现代编译器会自动执行这些优化。因此,迪迪埃的评论。
    猜你喜欢
    • 2011-12-23
    • 2020-10-06
    • 1970-01-01
    • 1970-01-01
    • 2013-01-17
    • 1970-01-01
    • 2019-09-12
    相关资源
    最近更新 更多