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

时间:2017-05-29 11:21:30

标签: c++ performance

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

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

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

快速:解决方案可以readline()中的python 3.6.0速度达到fgets(),甚至可以达到stdio.h std::getline()的最快速度。

欢迎使用Pure C解决方案。 C和C ++都提供了进一步处理的界面。

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

更新2 :感谢ZulanGalik的评论,他们都报告了fgets()POSIX getline()和{{1}之间的可比性能另一种可能的解决方案是使用更好的标准库实现,例如libstdc++。此外,这里有一个report声称std::getline的Visual C ++和libc ++实现没有得到很好的优化。

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

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

getline()的{​​{1}}可以处理任意长行,但速度有点慢。 C ++中是否有替代python中的<string>

readline()
  • 速度非常粗略地四舍五入,只是为了显示幅度,并且所有代码块都会运行几次,以确保这些值具有代表性。

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

  • 代码如下所示。

  

// 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 的python

readline()
  # 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

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; }

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; }

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; }

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; } ,然后使用cat

cin读取
cin.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; }

POSIX getline()

3 个答案:

答案 0 :(得分:6)

嗯,C标准库是C ++标准库的一个子集。来自C ++ 2014标准的n4296草案:

  

17.2 C标准库[library.c]

     

C ++标准库还提供了C标准库的功能,适当调整为   确保静态类型安全。

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

答案 1 :(得分:1)

是的,这是一种更快捷的读取线条和创建字符串的方法。

查询文件大小,然后将其加载到缓冲区中。然后迭代缓冲区,用nuls替换换行符,并将指针存储到下一行。

如果您的平台有可能调用将文件加载到内存中,那么速度会快得多。

答案 2 :(得分:1)

正如我评论的那样,在Linux&amp; 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;

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

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