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

4 投票
6 回答
5318 浏览
提问于 2025-04-16 04:30

我有一个数据文件,里面有将近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的程序更快一些?我很快意识到,把数据存储在元组的数组中比为每个条目创建一个类要好。

6 个回答

2

在C语言的实现中,你可以尝试用更底层的系统调用 open()/read()/close() 来替换 fopen()/fread()/fclose() 这些库函数。这样做可能会加快速度,因为 fread() 会进行很多缓冲处理,而 read() 则没有。

另外,减少 read() 的调用次数,使用更大的数据块,会减少系统调用的次数,这样你在用户空间和内核空间之间的切换也会减少。当你发出 read() 系统调用时(无论是通过 fread() 还是直接调用),内核会从磁盘读取数据,然后把这些数据复制到用户空间。如果你在代码中频繁发出这个系统调用,复制的过程就会变得很耗时。通过读取更大的数据块,你可以减少上下文切换和复制的次数。

不过要记住,read() 并不保证会返回你想要的确切字节数。这就是为什么在一个可靠和正确的实现中,你总是需要检查 read() 的返回值。

2

一种可能适用于C、C++和Python版本的方法是使用内存映射文件。这样做的最大好处是可以减少数据的重复处理,因为数据不需要从一个缓冲区复制到另一个缓冲区。在很多情况下,这样做还可以减少进行输入输出操作时的系统调用次数,从而提高效率。

3

几点说明:

  1. 你的C语言程序有点不太公平,它提前知道了文件大小,所以在分配内存的时候已经做好了准备...

  2. 在Python中,可以考虑使用 array.array('d') ... 分别为S和nu创建一个数组。然后再试试提前分配内存。

  3. 在Python中,把你的程序写成一个函数,然后调用它——这样访问函数内部的变量会比访问全局变量快很多。

撰写回答