8

ファイルを (ギグ単位で) 1 行ずつ読み書きするという課題に直面しています。

多くのフォーラム エントリとサイト (多数の SO を含む) を読んで、mmap はファイルを読み書きするための最速のオプションとして提案されました。ただし、readline と mmap の両方の手法を使用してコードを実装すると、mmap の方が遅くなります。これは、読み書きの両方に当てはまります。私は最大600 MBのファイルでテストしています。

私の実装では、行ごとに解析してから行をトークン化します。ファイル入力のみを提示します。

getlineの実装は次のとおりです。

void two(char* path) {

    std::ios::sync_with_stdio(false);
    ifstream pFile(path);
    string mystring;

    if (pFile.is_open()) {
        while (getline(pFile,mystring)) {
            // c style tokenizing
        }
    }
    else perror("error opening file");
    pFile.close();
}

ここにmmapがあります:

void four(char* path) {

    int fd;
    char *map;
    char *FILEPATH = path;
    unsigned long FILESIZE;

    // find file size
    FILE* fp = fopen(FILEPATH, "r");
    fseek(fp, 0, SEEK_END);
    FILESIZE = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    fclose(fp);

    fd = open(FILEPATH, O_RDONLY);

    map = (char *) mmap(0, FILESIZE, PROT_READ, MAP_SHARED, fd, 0);

    /* Read the file char-by-char from the mmap
     */
    char c;
    stringstream ss;

    for (long i = 0; i <= FILESIZE; ++i) {
        c = map[i];
        if (c != '\n') {
            ss << c;
        }
        else {
            // c style tokenizing
            ss.str("");
        }

    }

    if (munmap(map, FILESIZE) == -1) perror("Error un-mmapping the file");

    close(fd);

}

簡潔にするために、多くのエラー チェックを省略しました。

mmap の実装が正しくないため、パフォーマンスに影響がありますか? おそらく mmap は私のアプリケーションには理想的ではありませんか?

コメントやヘルプをありがとう!

4

4 に答える 4

14

mmap の真価は、ファイルを自由にシークし、その内容をポインターとして直接使用し、カーネル キャッシュ メモリからユーザー空間にデータをコピーするオーバーヘッドを回避できることです。ただし、コード サンプルはこれを利用していません。

ループでは、バッファを一度に 1 文字ずつスキャンし、stringstream. 文字列のstringstream長さがわからないため、プロセス中に何度か再割り当てする必要があります。この時点で、使用によるパフォーマンスの向上はすべて打ち切られましmmapた。標準の getline 実装でさえ、(GNU C++ 実装で 128 バイトのオンスタック バッファーを使用することにより) 複数の再割り当てを回避します。

mmap を最大限に活用したい場合:

  • 文字列をコピーしないでください。まったく。代わりに、ポインターを mmap バッファーに直接コピーします。
  • strnchrまたはなどの組み込み関数を使用memchrして、改行を検索します。これらは手作業で作成されたアセンブラやその他の最適化を利用して、ほとんどのオープン コードの検索ループよりも高速に実行されます。
于 2011-07-11T21:32:15.300 に答える
9

使用するように言った人はmmap、最新のマシンについてあまり知りません。

のパフォーマンス上の利点はmmap完全な神話です。Linus Torvaldsの言葉:

はい、メモリは「遅い」ですが、くそー、mmap()もそうです。

問題mmapは、マップされた領域のページに初めて触れるたびに、カーネルにトラップされ、実際にページがアドレス空間にマップされ、TLB が混乱することです。

を使用して一度に 8K の大きなファイルを読み取り、次に を使用して簡単なベンチマークを試してみてreadくださいmmap。(同じ 8K バッファを何度も使用します。)read実際には、その方が高速であることがほぼ確実にわかります。

あなたの問題は、カーネルからデータを取得することではありませんでした。その後のデータの扱い方に問題がありました。一度に 1 文字ずつ行う作業を最小限に抑えます。スキャンして改行を見つけてから、ブロックに対して単一の操作を実行します。個人的には、実装に戻りread、L1 キャッシュ (8K 程度) に収まるバッファーを使用 (および再使用) します。

または、少なくとも、単純なreadベンチマークmmapと比較して、プラットフォームで実際にどちらが速いかを確認します.

[アップデート]

トーバルズ氏からのコメントをさらにいくつか見つけました。

http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0728.html http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0775.html

サマリー:

それに加えて、実際の CPU TLB ミス コストなどもあります。これは、コピーを回避するためだけにメモリ管理を過度に賢くするのではなく、同じ領域を再読み込みするだけで回避できることがよくあります。

memcpy() (つまり、この場合は "read()") は、余分な複雑さをすべて回避するという理由だけで、多くの場合常に高速になります。他の場合では mmap() の方が高速になります。

read私の経験では、大きなファイルを順番に読み込んで処理することは、 /で適度なサイズのバッファを使用 (および再利用) したwrite方がmmap.

于 2011-07-11T21:54:59.730 に答える
0

stringstream識別した行を格納するためにsを使用しています。これはgetl​​ineの実装とは比較できず、stringstream自体がオーバーヘッドを追加します。他の提案として、文字列の先頭を、として格納でき、場合char*によっては行の長さ(または行の末尾へのポインタ)を格納できます。読み取りの本文は次のようになります。

char* str_start = map;
char* str_end;
for (long i = 0; i <= FILESIZE; ++i) {
        if (map[i] == '\n') {
            str_end = map + i;
            {
                // C style tokenizing of the string str_start to str_end
                // If you want, you can build a std::string like:
                // std::string line(str_start,str_end);
                // but note that this implies a memory copy.
            }
            str_start = map + i + 1;
        }
    }

また、各文字で何も処理しないため、これははるかに効率的であることに注意してください(バージョンでは、文字をに追加していましたstringstream)。

于 2011-07-11T21:48:02.307 に答える
0

memchr行末を見つけるために使用できます。stringstream一度に1 文字ずつ追加するよりもはるかに高速です。

于 2011-07-11T21:34:15.377 に答える