web-dev-qa-db-ja.com

C ++での高速テキストファイル読み取り

現在、多数の大きなテキストファイルの読み取りを含むプログラムをc ++で作成しています。各行には最大400.000行あり、極端な場合は1行に4000以上の文字があります。テストのためだけに、ifstreamとcplusplus.comが提供する実装を使用してファイルの1つを読み取りました。約60秒かかりましたが、これは長すぎます。今、私は疑問に思っていました、読書速度を改善する簡単な方法はありますか?

編集:私が使用しているコードは多かれ少なかれこれです:

string tmpString;
ifstream txtFile(path);
if(txtFile.is_open())
{
    while(txtFile.good())
    {
        m_numLines++;
        getline(txtFile, tmpString);
    }
    txtFile.close();
}

編集2:私が読んだファイルはわずか82 MBです。バッファリングを行うために知る必要があると考えたため、主に4000に達する可能性があると言いました。

編集3:回答ありがとうございます。しかし、私の問題を考えると改善の余地はあまりないようです。行数をカウントしたいので、readlineを使用する必要があります。 ifstreamをバイナリとしてインスタンス化しても、読み取りは速くなりませんでした。できる限り並列化を試みますが、少なくともうまくいくはずです。

編集4:だから明らかにできることがいくつかあります。これに多くの時間を割いてくださったseheに感謝します。 =)

58
Arne

更新:最初の回答の下にある(驚くべき)更新を必ず確認してください


メモリマップされたファイルは私によく役立ちました1

#include <boost/iostreams/device/mapped_file.hpp> // for mmap
#include <algorithm>  // for std::find
#include <iostream>   // for std::cout
#include <cstring>

int main()
{
    boost::iostreams::mapped_file mmap("input.txt", boost::iostreams::mapped_file::readonly);
    auto f = mmap.const_data();
    auto l = f + mmap.size();

    uintmax_t m_numLines = 0;
    while (f && f!=l)
        if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}

これはかなり速いはずです。

更新

このアプローチのテストに役立つ場合は、Boostを使用する代わりに、バージョンmmapを使用を直接使用します。 Coliruでライブ表示

#include <algorithm>
#include <iostream>
#include <cstring>

// for mmap:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

const char* map_file(const char* fname, size_t& length);

int main()
{
    size_t length;
    auto f = map_file("test.cpp", length);
    auto l = f + length;

    uintmax_t m_numLines = 0;
    while (f && f!=l)
        if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}

void handle_error(const char* msg) {
    perror(msg); 
    exit(255);
}

const char* map_file(const char* fname, size_t& length)
{
    int fd = open(fname, O_RDONLY);
    if (fd == -1)
        handle_error("open");

    // obtain file size
    struct stat sb;
    if (fstat(fd, &sb) == -1)
        handle_error("fstat");

    length = sb.st_size;

    const char* addr = static_cast<const char*>(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0u));
    if (addr == MAP_FAILED)
        handle_error("mmap");

    // TODO close fd at some point in time, call munmap(...)
    return addr;
}

更新

パフォーマンスの最後のビットは、GNU coreutils wc。]のソースを見ると見つかりました。 wcは、メモリマップファイルでの約84%の時間で実行されます上記:

static uintmax_t wc(char const *fname)
{
    static const auto BUFFER_SIZE = 16*1024;
    int fd = open(fname, O_RDONLY);
    if(fd == -1)
        handle_error("open");

    /* Advise the kernel of our access pattern.  */
    posix_fadvise(fd, 0, 0, 1);  // FDADVICE_SEQUENTIAL

    char buf[BUFFER_SIZE + 1];
    uintmax_t lines = 0;

    while(size_t bytes_read = read(fd, buf, BUFFER_SIZE))
    {
        if(bytes_read == (size_t)-1)
            handle_error("read failed");
        if (!bytes_read)
            break;

        for(char *p = buf; (p = (char*) memchr(p, '\n', (buf + bytes_read) - p)); ++p)
            ++lines;
    }

    return lines;
}

1 例参照ここのベンチマーク: C++でスペースで区切られたフロートをすばやく解析する方法?

71
sehe

ハードドライブがSSDではない場合、4000 * 400,000 = 1.6 GBで、約100 MB/sのシーケンシャルリードが発生する可能性があります。 I/Oでちょうど16秒です。

使用している特定のコードやこれらのファイルを解析する方法について詳しく説明していないので(行ごとに読み取る必要がありますか、システムには多くのRAMがありますかファイル全体を大きなRAMバッファーに読み込んでから解析しますか?)プロセスを高速化するためにできることはほとんどありません。

メモリマップファイルは、ファイルを順番に読み取るときにパフォーマンスの改善を提供しません。 「getline」を使用するのではなく、新しい行の大きなチャンクを手動で解析することで改善される可能性があります。

[〜#〜] edit [〜#〜]いくつかの学習を行った後(@seheに感謝)。これは、私が使用する可能性のあるメモリマップソリューションです。

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

int main() {
    char* fName = "big.txt";
    //
    struct stat sb;
    long cntr = 0;
    int fd, lineLen;
    char *data;
    char *line;
    // map the file
    fd = open(fName, O_RDONLY);
    fstat(fd, &sb);
    //// int pageSize;
    //// pageSize = getpagesize();
    //// data = mmap((caddr_t)0, pageSize, PROT_READ, MAP_PRIVATE, fd, pageSize);
    data = mmap((caddr_t)0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    line = data;
    // get lines
    while(cntr < sb.st_size) {
        lineLen = 0;
        line = data;
        // find the next line
        while(*data != '\n' && cntr < sb.st_size) {
            data++;
            cntr++;
            lineLen++;
        }
        /***** PROCESS LINE *****/
        // ... processLine(line, lineLen);
    }
    return 0;
}
10
Louis Ricci

ニール・カーク、残念ながらあなたのコメントに返信することはできません(評判が足りません)が、ifstreamのパフォーマンステストを行いました。テキストストリームを1行ずつ読み取るパフォーマンスはまったく同じです。

std::stringstream stream;
std::string line;
while(std::getline(stream, line)) {
}

これには、106MBファイルで1426msかかります。

std::ifstream stream;
std::string line;
while(ifstream.good()) {
    getline(stream, line);
}

これには、同じファイルで1433msかかります。

代わりに、次のコードの方が高速です。

const int MAX_LENGTH = 524288;
char* line = new char[MAX_LENGTH];
while (iStream.getline(line, MAX_LENGTH) && strlen(line) > 0) {
}

これには、同じファイルで884msかかります。バッファの最大サイズ(つまり、入力ファイルの各行の最大長)を設定する必要があるため、少し注意が必要です。

4
user2434119

すべてのファイルを同時に読み取る必要がありますか? (たとえば、アプリケーションの開始時)

その場合は、操作の並列化を検討してください。

いずれにせよ、バイナリストリームを使用するか、データブロックに read を使用することを検討してください。

2
utnapistim

Random file accessを使用するか、binary modeを使用します。シーケンシャルの場合、これは大きいですが、それでもあなたが読んでいるものに依存します。

1
Shumail

少なくとも競合プログラミングのバックグラウンドを持つ人として、私はあなたに伝えることができます:少なくともCの主なコストの整数解析のような単純なことは、ファイルストリームをロックすることです(デフォルトではマルチスレッドで行われます)。代わりに_unlocked_stdio_バージョンを使用してください(fgetc_unlocked()fread_unlocked())。 C++の場合、一般的な言い方はstd::ios::sync_with_stdio(false)を使用することですが、_unlocked_stdio_ほど高速かどうかはわかりません。

参考のために、ここに私の標準の整数解析コードがあります。主にストリームをロックしないために言ったように、それはlot scanfよりも高速です。私にとって、これは、非常に保守的な借金なしで、以前に使用した最高の手動コーディングmmapまたはカスタムバッファバージョンと同じくらい高速でした。

_int readint(void)
{
        int n, c;
        n = getchar_unlocked() - '0';
        while ((c = getchar_unlocked()) > ' ')
                n = 10*n + c-'0';
        return n;
}
_

(注:これは、2つの整数の間に数字以外の文字が1つだけある場合にのみ機能します)。

そしてもちろん、可能であればメモリの割り当てを避けてください...

1
Jo So