2017-05-29 72 views
7

有一个6.53 GiB的大文本文件。它的每一行都可以是数据行或注释行。注释行通常很短,少于80个字符,而数据行包含超过200万个字符并且长度可变。如何从文本文件中读取极长的文本文件,在C++中快速安全地使用?

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

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

快速:该解决方案可以在python 3.6.0一样快readline()实现,或甚至尽可能快的stdio.hfgets()

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


更新1:由于短暂而宝贵的意见,从Basile Starynkevitch,完美的解决方案出现:POSIX getline()。由于进一步的处理只涉及从字符到数字的转换,并且不使用字符串类的许多特征,所以在这个应用程序中,char数组就足够了。


更新2:由于从ZulanGalik意见,谁报告都std::getline()之中,fgets()POSIX getline()相当的性能,另一种可能的解决方案是使用一个更好的标准库的实现,如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)的默认设置下运行,因此使用libc++


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

<string>getline()能够处理任意长的线,但有点慢。在python中有没有替代C++的readline()

// 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 
  • 速度四舍五入非常粗略,只显示幅度,并且所有的代码块被多次运行,以确保该值代表。

  • “-O0/-O2”是指速度对于既优化级别

  • 代码如下所示非常相似。


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 

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

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

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

使用catread(),然后从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()

// 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); 
} 
+5

你检查了这一点:https://stackoverflow.com/questions/9371238/why-is-reading-lines-from-stdin-much-slower-in-c-than-python?rq=1? – Rene

+1

假设一个Linux系统,你也应该基准[getline(3)](http://man7.org/linux/man-pages/man3/getline.3.html) –

+0

你为什么不添加tghe Brainfuck标签? – Olaf

回答

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; 

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

另外getline可能会失败(例如,如果它调用了失败的malloc)并且您可能需要合理处理该故障。

5

那么,C标准库是C++标准库的子集。从n4296草案C++ 2014标准:

17.2 C标准库[LIBRARY.C]

C++标准库还使得C标准库的确保静态类型提供的设施,适当调整 安全。

所以只要你在评论一个性能瓶颈,需要解释一下,这是完全正常的C++程序使用fgets - 只要你应该仔细将其封装在一个实用工具类,以保持OO高级别的结构。

+0

我希望这篇文章被低估,因为它建议使用C++的C库函数,但我天真地希望我能得到一条评论...... –

+1

根据标准,这些库也是'C++'的一部分,所以我不了解倒票。 – Galik

+2

我觉得这个答案的唯一部分是令人讨厌的,那就是*“你应该仔细地将它封装在一个实用类中,以保持OO高层结构。”*。我不明白为什么有必要在一个类中包装'fgets'。 C++是一种多范式语言:并非所有事物都适合OOP范式。它不是海峡夹克。不过,无论如何,对于“实用工具”类来说,没有什么特别面向对象。如果'fgets'真的可以从面向对象中受益,那么你只会打扰一个包装器,我不确定那会是什么。 –

1

是的,有更快的方式来读取线条和创建字符串。

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

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

+2

目前尚不清楚会更快。一位朋友通过使用内存映射“优化”了我们的覆盖管理代码(Pr1me软件的一个VAX端口 - 我们正在谈论80年代初)。这并不是那么快。他切换到QIOW从磁盘读取到一个固定位的内存 - 它要快得多。你的方法将涉及两次通过数据 - 这可能是无法接受的昂贵。 –

相关问题