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

问题描述 投票:0回答:6

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

python c io dataset
6个回答
4
投票

几点:

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

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

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


3
投票

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


3
投票

在 C 实现中,您可以尝试将

fopen()
/
fread()
/
fclose()
库函数替换为较低级别的系统调用
open()
/
read()
/
close()
。加速可能来自于以下事实:
fread()
进行了大量缓冲,而
read()
则没有。

此外,使用较大的块更少地调用

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

请记住,

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


1
投票

您在

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
投票
不要处理C,除非

绝对必要。为了尝试模拟OP的需求,我有一个方便的3列ASCII-only

.txt
文件,由
'='
而不是
' '
分隔

跨越一些

148 million rows

 几乎 
7.6 GiB

  • 这只是我用于代码验证检查的一个大素数文件

  • 这 3 列是素数的以 2 为底的对数,固定宽度为 5 个小数位,数字本身,采用直接 ASCII 十进制数字,以及该值的十六进制。

将同一个文件传输 3 次,得到一些

444 mn rows

 的合成输入(距离 
500 mn
 提到的 
OP
 目标不远),并使用 
awk
 收集它的一些基本统计数据 — 
(总结)第一列作为浮点值, 以及第二列和第三列的列宽分组计数)


awk 仅用

444 mn rows

 处理了 22.8 GiB
78.26 secs
 合成平面文本文件,没有预制索引。以相同的速率处理 900 万行只会:

1.586 秒 -

当 shell 脚本具有这样的速度时,

C

 可能会带来更多麻烦。


( for ___ in $$ $$$$ $$$$$$; do; pv -q < "$__"; done | pvE 0.1 in0 | mawk2 ; ) 77.05s user 7.66s system 108% cpu 1:18.26 total gcat -b 0.00s user 0.00s system 0% cpu 1:18.26 total

(出于可读性目的,删除了数百行)

log2(_)-sum :: ~ 29790561307.14320755004882812-bits 11515 3 3 34545 11370 3 6 68655 10369 3 9 99762 10154 3 12 130224 10098 3 15 160518 ... 2222 3 4449 14761953 2000 6 6123 18273609 1111 165 69522 105717363 1000 177 90270 127527948 999 252 90522 127779696 888 330 124305 159575346 777 432 170421 197709843 666 975 232761 242405211 555 924 341022 308075370 444 1878 553710 412565496 333 4317 969051 571686960 222 35214 2756478 1036817178 111 53748 7457043 1796338806 99 80262 8224686 1876339980 88 95472 9469041 1991870625 77 124485 10676904 2090514033 66 171246 12489018 2217919404 55 280368 14771130 2354031702 44 827844 20983845 2649656613 33 1502661 30431553 3001060344 22 7765587 59774289 3729032556 11 21562245 433469169 9124979175 9 2761992 443583456 9223360053 8 414552 443998008 9226676469 7 295692 444293700 9228746313 6 148572 444442272 9229637745 5 23955 444466227 9229757520 4 3183 444469410 9229770252 3 429 444469839 9229771539 2 54 444469893 9229771647

3333 3 855 3876831 1111 114 38097 56141871 1000 153 54039 72918171 999 174 54213 73091997 888 159 76497 94118478 777 258 109593 121425312 666 537 157764 155947470 555 504 231174 200476329 444 1212 366900 267599124 333 4389 685701 388875105 222 12837 1553802 619755666 111 61842 6291783 1374531276 99 66285 7030575 1451567319 88 67101 7769130 1520132172 77 149163 8927163 1614135813 66 150294 10389507 1718099022 55 243546 12483837 1842271380 44 339105 15356979 1981646316 33 692868 23890017 2301350703 22 1083543 37376904 2664284580 11 105021729 327544728 6535425114 9 19497831 437237055 7612850553 8 5280768 442517823 7655096697 7 1395681 443913504 7664866464 6 373872 444287376 7667109696 5 163302 444450678 7667926206 4 17544 444468222 7667996382 3 1530 444469752 7668000972 2 141 444469893 7668001254

mawk2 ' BEGIN { CONVFMT = OFMT = "%.250g" _ = +(FS = "=" ) OFS = "=| " } { __ += $++_ ___[length($++_) ]++ ____[-_ + \ length($(_--+_--))]++ } END { printf "\f\f\r\t log2(_)-sum :: ~ %29.17f-bits\n\n", __ _____(____, _____(___)) } function _____(___, _, __, ____, _______) { _ = "" for (_ in ___) { _ = length(___) break } if (_) { NF *= NF = (__ ^= NF = _<_)\ + (____ = __) for (_ in ___)__ < +_ && __ = +_ _______ = "(000|^(1+|2+|3+|4+|5+|6+|7+|8+|9+|.....+))$" for (_ = ++__; --_; ) { if (__ = +___[_]) { $NF += ($____ = _) * ($(____+____) = __) $(NF-____) += __ if (_ ~ _______) print } } print "\f\r" (_=(_="---=| ") _) _ (_) _ "---\f\n" return }'
    

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.