2017-05-29 74 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從磁盤讀取到一個固定位的內存 - 它要快得多。你的方法將涉及兩次通過數據 - 這可能是無法接受的昂貴。 –

相關問題