读取文本列的大型数据文件的最快方法是什么?

时间:2010-09-23 13:53:40

标签: python c io dataset

我有一个近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例程?我很快意识到将数据存储在元组数组中比为每个条目实例化一个类要好。

5 个答案:

答案 0 :(得分:4)

有些观点:

  1. 你的C例程是作弊;正在向文件大小提示,并且正在预先分配......

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

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

答案 1 :(得分:3)

在C实现中,您可以尝试为较低级别的系统调用fopen() / fread()交换fclose() / open() / read()库函数/ close()。加速可能来自fread()进行大量缓冲而read()没有进行缓冲的事实。

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

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

答案 2 :(得分:3)

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

答案 3 :(得分:1)

1中的BPLfread()参数错误的方式(你拥有它的方式,它可以读取你不测试的部分行) )。您还应该在尝试之前测试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);

答案 4 :(得分:-1)

考虑到你需要做的次数,另一种可能的加速是使用指向S和nu的指针而不是索引到数组中,例如,

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

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