如何在C++中快速安全地从文本文件中读取极长行?

Eli*_*4ph 8 c++ performance

有一个6.53 GiB的大文本文件.它的每一行都可以是数据行或注释行.注释行通常很短,少于80个字符,而数据行包含超过200万个字符并且是可变长度的.

考虑到每条数据线需要作为一个单元来处理,有一种简单的方法可以在C++中安全快速地读取线路吗?

安全(对于可变长度数据线是安全的):该解决方案同样易于使用std::getline().由于长度在变化,因此希望避免额外的内存管理.

:该解决方案可以快速实现与readline()python 3.6.0,甚至一样快fgets()stdio.h.

欢迎使用Pure C解决方案.用于进一步处理的接口以C和C++提供.


更新1:感谢Basile Starynkevitch的简短而宝贵的评论,出现了完美的解决方案:POSIX getline().由于进一步处理只涉及从字符转换为数字而不使用字符串类的许多功能,因此在此应用程序中使用char数组就足够了.


更新2:感谢来自评论ZulanGalik,谁既报告中相当的性能std::getline(),fgets()并且POSIX getline(),另一种可能的解决方案是使用一个更好的标准库的实现,例如libstdc++.此外,这里有一份报告声称Visual C++和libc ++实现std::getline没有得到很好的优化.

从中移动libc++libstdc++改变结果很多.与libstdc ++ 3.4.13/Linux 2.6.32在不同的平台上POSIX getline(),std::getline()fgets()显示可比的性能.开始时,代码在Xcode 8.3.2(8E2002)中的clang的默认设置下运行,因此libc++被使用.


更多细节和一些努力(很长):

getline()<string>可以处理任意长的线,但有点慢.readline()在python 中有C++的替代品吗?

// benchmark on Mac OS X with libc++ and SSD:
readline() of python                         ~550 MiB/s

fgets() of stdio.h, -O0 / -O2               ~1100 MiB/s

getline() of string, -O0                      ~27 MiB/s
getline() of string, -O2                     ~150 MiB/s
getline() of string + stack buffer, -O2      ~150 MiB/s

getline() of ifstream, -O0 / -O2             ~240 MiB/s
read() of ifstream, -O2                      ~340 MiB/s

wc -l                                        ~670 MiB/s

cat data.txt | ./read-cin-unsync              ~20 MiB/s

getline() of stdio.h (POSIX.1-2008), -O0    ~1300 MiB/s
Run Code Online (Sandbox Code Playgroud)
  • 速度非常粗略地舍入,仅显示幅度,并且所有代码块都运行多次以确保值具有代表性.

  • '-O0/-O2'表示两种优化级别的速度非常相似

  • 代码如下所示.


readline() python

# readline.py

import time
import os

t_start = time.perf_counter()

fname = 'data.txt'
fin = open(fname, 'rt')

count = 0

while True:
    l = fin.readline()
    length = len(l)
    if length == 0:     # EOF
        break
    if length > 80:     # data line
        count += 1

fin.close()

t_end = time.perf_counter()
time = t_end - t_start

fsize = os.path.getsize(fname)/1024/1024   # file size in MiB
print("speed: %d MiB/s" %(fsize/time))
print("reads %d data lines" %count)

# run as `python readline.py` with python 3.6.0
Run Code Online (Sandbox Code Playgroud)

fgets()stdio.h

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

int main(int argc, char* argv[]){
  clock_t t_start = clock();

  if(argc != 2) {
    fprintf(stderr, "needs one input argument\n");
    return EXIT_FAILURE;
  }

  FILE* fp = fopen(argv[1], "r");
  if(fp == NULL) {
    perror("Failed to open file");
    return EXIT_FAILURE;
  }

  // maximum length of lines, determined previously by python
  const int SIZE = 1024*1024*3;
  char line[SIZE];

  int count = 0;
  while(fgets(line, SIZE, fp) == line) {
    if(strlen(line) > 80) {
      count += 1;
    }
  }

  clock_t t_end = clock();

  const double fsize = 6685;  // file size in MiB

  double time = (t_end-t_start) / (double)CLOCKS_PER_SEC;

  fprintf(stdout, "takes %.2f s\n", time);
  fprintf(stdout, "speed: %d MiB/s\n", (int)(fsize/time));
  fprintf(stdout, "reads %d data lines\n", count);

  return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

getline()<string>

// readline-string-getline.cpp
#include <string>
#include <fstream>
#include <iostream>
#include <ctime>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]) {
  clock_t t_start = clock();

  if(argc != 2) {
    fprintf(stderr, "needs one input argument\n");
    return EXIT_FAILURE;
  }

  // manually set the buffer on stack
  const int BUFFERSIZE = 1024*1024*3;   // stack on my platform is 8 MiB
  char buffer[BUFFERSIZE];
  ifstream fin;
  fin.rdbuf()->pubsetbuf(buffer, BUFFERSIZE);
  fin.open(argv[1]);

  // default buffer setting
  // ifstream fin(argv[1]);

  if(!fin) {
    perror("Failed to open file");
    return EXIT_FAILURE;
  }

  // maximum length of lines, determined previously by python
  const int SIZE = 1024*1024*3;
  string line;
  line.reserve(SIZE);

  int count = 0;
  while(getline(fin, line)) {
    if(line.size() > 80) {
      count += 1;
    }
  }

  clock_t t_end = clock();

  const double fsize = 6685;  // file size in MiB

  double time = (t_end-t_start) / (double)CLOCKS_PER_SEC;

  fprintf(stdout, "takes %.2f s\n", time);
  fprintf(stdout, "speed: %d MiB/s\n", (int)(fsize/time));
  fprintf(stdout, "reads %d data lines\n", count);

  return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

getline()ifstream

// readline-ifstream-getline.cpp
#include <fstream>
#include <iostream>
#include <ctime>
#include <cstdlib>

using namespace std;

int main(int argc, char* argv[]) {
  clock_t t_start = clock();

  if(argc != 2) {
    fprintf(stderr, "needs one input argument\n");
    return EXIT_FAILURE;
  }

  ifstream fin(argv[1]);
  if(!fin) {
    perror("Failed to open file");
    return EXIT_FAILURE;
  }

  // maximum length of lines, determined previously by python
  const int SIZE = 1024*1024*3;
  char line[SIZE];

  int count = 0;
  while(fin.getline(line, SIZE)) {
    if(strlen(line) > 80) {
      count += 1;
    }
  }

  clock_t t_end = clock();

  const double fsize = 6685;  // file size in MiB

  double time = (t_end-t_start) / (double)CLOCKS_PER_SEC;

  fprintf(stdout, "takes %.2f s\n", time);
  fprintf(stdout, "speed: %d MiB/s\n", (int)(fsize/time));
  fprintf(stdout, "reads %d data lines\n", count);

  return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

read()ifstream

// seq-read-bin.cpp
// sequentially read the file to see the speed upper bound of
// ifstream

#include <iostream>
#include <fstream>
#include <ctime>

using namespace std;


int main(int argc, char* argv[]) {
  clock_t t_start = clock();

  if(argc != 2) {
    fprintf(stderr, "needs one input argument\n");
    return EXIT_FAILURE;
  }

  ifstream fin(argv[1], ios::binary);

  const int SIZE = 1024*1024*3;
  char str[SIZE];

  while(fin) {
    fin.read(str,SIZE);
  }

  clock_t t_end = clock();
  double time = (t_end-t_start) / (double)CLOCKS_PER_SEC;

  const double fsize = 6685;  // file size in MiB

  fprintf(stdout, "takes %.2f s\n", time);
  fprintf(stdout, "speed: %d MiB/s\n", (int)(fsize/time));

  return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

使用cat,然后从阅读cincin.sync_with_stdio(false)

#include <iostream>
#include <ctime>
#include <cstdlib>

using namespace std;

int main(void) {
  clock_t t_start = clock();

  string input_line;

  cin.sync_with_stdio(false);

  while(cin) {
    getline(cin, input_line);
  }

  double time = (clock() - t_start) / (double)CLOCKS_PER_SEC;

  const double fsize = 6685;  // file size in MiB

  fprintf(stdout, "takes %.2f s\n", time);
  fprintf(stdout, "speed: %d MiB/s\n", (int)(fsize/time));

  return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)

POSIX getline()

// readline-c-getline.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(int argc, char *argv[]) {

  clock_t t_start = clock();

  char *line = NULL;
  size_t len = 0;
  ssize_t nread;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s <file>\n", argv[1]);
    exit(EXIT_FAILURE);
  }

  FILE *stream = fopen(argv[1], "r");
  if (stream == NULL) {
    perror("fopen");
    exit(EXIT_FAILURE);
  }

  int length = -1;
  int count = 0;
  while ((nread = getline(&line, &len, stream)) != -1) {
    if (nread > 80) {
      count += 1;
    }
  }

  free(line);
  fclose(stream);

  double time = (clock() - t_start) / (double)CLOCKS_PER_SEC;
  const double fsize = 6685;  // file size in MiB
  fprintf(stdout, "takes %.2f s\n", time);
  fprintf(stdout, "speed: %d MiB/s\n", (int)(fsize/time));
  fprintf(stdout, "reads %d data lines.\n", count);
  // fprintf(stdout, "length of MSA: %d\n", length-1);

  exit(EXIT_SUCCESS);
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*sta 6

那么,C标准库是C++标准库的一个子集.来自C++ 2014标准的n4296草案:

17.2 C标准库[library.c]

C++标准库还提供C标准库的功能,经过适当调整以确保静态类型安全.

因此,如果您在评论中解释性能瓶颈需要它,那么fgets在C++程序中使用它是完全正确的- 只需将其小心地封装在实用程序类中,以保留OO高级结构.

  • 我发现令人反感的答案的唯一部分是*"你应该小心地将它封装在实用程序类中,以保留OO高级结构."*.我不明白为什么有必要在课堂上包装`fgets`.C++是一种多范式语言:并非所有内容都必须符合OOP范式.它不是一件紧身衣.并不是说有任何特别是面向对象的"实用"类.如果`fgets`真的可以从面向对象中受益,你只会打扰一个包装器,我不确定那是怎么回事. (2认同)

Bas*_*tch 1

正如我所评论的,在 Linux 和 POSIX 系统上,您可以考虑使用getline(3);我猜想以下代码可以编译为 C 和 C++(假设您确实有一些有效的fopen-ed FILE*fil;...)

char* linbuf = NULL; /// or nullptr in C++
size_t linsiz = 0;
ssize_t linlen = 0;

while((linlen=getline(&linbuf, &linsiz,fil))>=0) {
  // do something useful with linbuf; but no C++ exceptions
}
free(linbuf); linsiz=0;
Run Code Online (Sandbox Code Playgroud)

我想这可能适用于(或很容易适应)C++。但是,请注意 C++ 异常,它们不应该通过 while 循环(或者您应该确保有适当的析构函数 orcatch正在执行free(linbuf);)。

getline可能会失败(例如,如果它调用失败malloc),您可能需要明智地处理该失败。