9

テキスト ファイルを処理するときは、最初に内容 (またはその一部) を std::string または char 配列に読み込む方が効率的であると常に想定してきました。ブロックは、1 文字のサイズよりもはるかに大きくなります。ただし、最近の OS は実際にはファイルから直接読み取らないことが多いため、入力を手動でバッファリングしてもほとんどメリットがないと聞いています。

テキスト ファイル内の特定の文字の数を調べたいとします。以下は非効率でしょうか?

while (fin.get(ch)) {
    if (ch == 'n')
        ++char_count;
}

確かに、ファイルサイズにもよると思いますが、最善のアプローチについて一般的なルールを持っている人はいますか?

4

3 に答える 3

12

ここでの多くは、あなた/あなたのアプリケーションにとってパフォーマンスが実際にどれほど重要かによって異なります。これは、扱うファイルの大きさに依存する傾向があります。数十キロバイトまたは数百キロバイトのようなものを扱う場合は、通常、機能する最も単純なコードを書くだけでよく、あまり心配する必要はありません。つまり、できることはすべて基本的に瞬時に実行されるため、コードを最適化してもあまり効果がありません。

一方、大量のデータ (数十メガバイト以上) を処理している場合、効率の差はかなり大きくなります。それをバイパスするためにかなり具体的な手順を踏まない限り(を使用するなどread)、すべての読み取りがバッファリングされます-しかし、それはすべてが同じ速度になるという意味ではありませ(または必ずしも同じ速度に非常に近い).

たとえば、基本的にあなたが尋ねたことを実行するためのいくつかの異なる方法の簡単なテストを試してみましょう:

#include <stdio.h>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <fstream>
#include <time.h>
#include <string>
#include <algorithm>

unsigned count1(FILE *infile, char c) { 
    int ch;
    unsigned count = 0;

    while (EOF != (ch=getc(infile)))
        if (ch == c)
            ++count;
    return count;
}

unsigned int count2(FILE *infile, char c) { 
    static char buffer[4096];
    int size;
    unsigned int count = 0;

    while (0 < (size = fread(buffer, 1, sizeof(buffer), infile)))
        for (int i=0; i<size; i++)
            if (buffer[i] == c)
                ++count;
    return count;
}

unsigned count3(std::istream &infile, char c) {    
    return std::count(std::istreambuf_iterator<char>(infile), 
                    std::istreambuf_iterator<char>(), c);
}

unsigned count4(std::istream &infile, char c) {    
    return std::count(std::istream_iterator<char>(infile), 
                    std::istream_iterator<char>(), c);
}

template <class F, class T>
void timer(F f, T &t, std::string const &title) { 
    unsigned count;
    clock_t start = clock();
    count = f(t, 'N');
    clock_t stop = clock();
    std::cout << std::left << std::setw(30) << title << "\tCount: " << count;
    std::cout << "\tTime: " << double(stop-start)/CLOCKS_PER_SEC << "\n";
}

int main() {
    char const *name = "test input.txt";

    FILE *infile=fopen(name, "r");

    timer(count1, infile, "ignore");

    rewind(infile);
    timer(count1, infile, "using getc");

    rewind(infile);
    timer(count2, infile, "using fread");

    fclose(infile);

    std::ifstream in2(name);
    in2.sync_with_stdio(false);
    timer(count3, in2, "ignore");

    in2.clear();
    in2.seekg(0);
    timer(count3, in2, "using streambuf iterators");

    in2.clear();
    in2.seekg(0);
    timer(count4, in2, "using stream iterators");

    return 0;
}

入力として約 44 メガバイトのファイルを使用してこれを実行しました。VC++2012 でコンパイルすると、次の結果が得られました。

ignore                          Count: 400000   Time: 2.08
using getc                      Count: 400000   Time: 2.034
using fread                     Count: 400000   Time: 0.257
ignore                          Count: 400000   Time: 0.607
using streambuf iterators       Count: 400000   Time: 0.608
using stream iterators          Count: 400000   Time: 5.136

同じ入力を使用しますが、g++ 4.7.1 でコンパイルします。

ignore                          Count: 400000   Time: 0.359
using getc                      Count: 400000   Time: 0.339
using fread                     Count: 400000   Time: 0.243
ignore                          Count: 400000   Time: 0.697
using streambuf iterators       Count: 400000   Time: 0.694
using stream iterators          Count: 400000   Time: 1.612

そのため、すべての読み取りがバッファリングされているにもかかわらず、g++ では約 8:1、VC++ では約 20:1 の変動が見られます。もちろん、入力を読み取るすべての可能な方法をテストしたわけではありません。より多くの読書テクニックをテストした場合、より広い時間範囲が見られるとは思えませんが、それについては間違っている可能性があります. 実行するかどうかにかかわらず、少なくとも大量のデータを処理している場合は、処理速度を向上させるために 1 つの手法を別の手法よりも選択することを正当化できる十分なバリエーションが見られます。

于 2012-12-10T21:58:45.087 に答える
0

それはコンテキストに大きく依存します。コードを取り巻くコンテキストが存在しないため、それを言うのは困難です。

間違いなく、他の人が言っているように、OS はおそらくファイルの少なくとも一部をキャッシュしています...ただし、ユーザーとカーネルの間を行き来するのは高価であり、おそらくボトルネックの原因です。

このコードの前に挿入fin.rdbuf()->pubsetbuf(NULL, 65536);すると、大幅なスピードアップに気付くかもしれません。これは、標準ライブラリがカーネルから一度に 65536 バイトを読み取ろうとするヒントであり、文字ごとにユーザーとカーネルの間を行き来するのではなく、後で使用できるように保存します。

于 2015-05-22T03:36:16.893 に答える
0

いいえ、あなたのコードは効率的です。ファイルは順次読み取られることを意図しています。バックグラウンドでは、着信データ ストリームをバッファリングするために RAM ブロックが予約されます。実際、ファイル全体が読み取られる前にデータの処理を開始するため、while ループは少し早く完了するはずです。さらに、コンピュータのメイン RAM をはるかに超えるファイルを問題なく処理できます。

編集: 驚いたことに、ジェリーの番号がうまくいきませんでした。チャンクでの読み取りと解析によって得られる効率は、ファイルからの読み取りのコストよりもはるかに小さいと想定していました。その時間が費やされている場所と、ファイルがキャッシュされていない場合の変動がどれだけ少ないかを知りたいです。それにもかかわらず、私はこれよりもジェリーの答えをお勧めしなければなりません。特に彼が指摘しているように、パフォーマンスの問題があることがわかるまで、本当に心配する必要はありません。

于 2012-12-10T20:50:15.520 に答える