Какой самый быстрый способ чтения в большом файле данных текстовых столбцов? - PullRequest
4 голосов
/ 23 сентября 2010

У меня есть файл данных почти 9 миллионов строк (скоро будет более 500 миллионов строк), и я ищу самый быстрый способ его чтения. Пять выровненных столбцов дополняются пробелами, поэтому я знаю, где в каждой строке искать два поля, которые я хочу. Моя процедура 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 ]

4 голосов
/ 23 сентября 2010

Некоторые баллы:

  1. Твоя рутина C обманывает; он сглаживается с размером файла и предварительно выделяет ...

  2. Python: рассмотрите возможность использования array.array ('d') ... по одному для S и nu. Затем попробуйте предварительное распределение.

  3. Python: напишите вашу подпрограмму как функцию и вызовите ее - доступ к локальным переменным функции происходит быстрее, чем к глобальным переменным модуля.

3 голосов
/ 23 сентября 2010

Подход, который, вероятно, может быть применен к версиям C, C ++ и python, заключается в использовании карты памяти файла.Самым значительным преимуществом является то, что он может уменьшить объем двойной обработки данных при их копировании из одного буфера в другой.Во многих случаях есть также преимущества из-за сокращения числа системных вызовов для ввода / вывода.

3 голосов
/ 23 сентября 2010

В реализации C вы можете попробовать поменять местами библиотечные функции fopen() / fread() / fclose() для системных вызовов нижнего уровня open() / read() / close(). Ускорение может быть связано с тем, что fread() выполняет большую буферизацию, а read() - нет.

Кроме того, менее частый вызов read() с большими порциями уменьшит количество системных вызовов и, следовательно, вы будете меньше переключаться между пользовательским пространством и пространством ядра. То, что делает ядро, когда вы выполняете системный вызов read() (не имеет значения, был ли он вызван из библиотечной функции fread()), читает данные с диска и затем копирует их в пространство пользователя. Копирование становится дорогостоящим, если вы часто используете системный вызов в своем коде. Читая большие фрагменты, вы получите меньше переключений контекста и меньше копий.

Имейте в виду, что read() не гарантированно возвращает блок с точным количеством байтов, которое вы хотели. Вот почему в надежной и правильной реализации вы всегда должны проверять возвращаемое значение read().

1 голос
/ 24 сентября 2010

У вас есть 1 и BPL аргументы в fread() неверно (он может читать частичную строку, которую вы не проверяете).Вам также следует проверить возвращаемое значение 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);
0 голосов
/ 23 сентября 2010

Другим возможным ускорением, учитывая количество раз, которое вам нужно сделать, является использование указателей на S и nu вместо индексации в массивы, например,

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

Кроме того, поскольку вывсегда преобразуя из char в double в одних и тех же местах в buf, предварительно вычисляйте адреса вне цикла вместо того, чтобы вычислять их каждый раз в цикле.

...