web-dev-qa-db-ja.com

istream / ostreamが遅いのはなぜですか

http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly の50:40に、Andrei Alexandrescuがいかに効率が悪い/遅いかについて冗談を言っていますistreamです。

私は過去にostreamが遅く、fwriteがかなり速い(メインループを1回実行するときに何秒も短縮する)という問題を抱えていましたが、なぜなのか理解していませんでした。

C++でistreamとostreamが遅くなる原因は何ですか?または、少なくとも同等にニーズを満たす他のもの(fread/fget、fwriteなど)と比較して遅い。

35
user34537

おそらく、これはあなたが何を扱っているかについていくつかのアイデアを与えることができます:

#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[8192];
    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);
}

unsigned int count5(std::istream &infile, char c) {
    static char buffer[8192];
    unsigned int count = 0;

    while (infile.read(buffer, sizeof(buffer)))
        count += std::count(buffer, buffer+infile.gcount(), c);
    count += std::count(buffer, buffer+infile.gcount(), c);
    return count;
}

unsigned count6(std::istream &infile, char c) {
    unsigned int count = 0;
    char ch;

    while (infile >> ch)
        if (ch == c)
            ++count;
    return count;
}

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 = "equivs2.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);
    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");

    in2.clear();
    in2.seekg(0);
    timer(count5, in2, "using istream::read");

    in2.clear();
    in2.seekg(0);
    timer(count6, in2, "using operator>>");

    return 0;
}

これを実行すると、次のような結果が得られます(MS VC++を使用)。

ignore                          Count: 1300     Time: 0.309
using getc                      Count: 1300     Time: 0.308
using fread                     Count: 1300     Time: 0.028
ignore                          Count: 1300     Time: 0.091
using streambuf iterators       Count: 1300     Time: 0.091
using stream iterators          Count: 1300     Time: 0.613
using istream::read             Count: 1300     Time: 0.028
using operator>>                Count: 1300     Time: 0.619

そしてこれ(MinGWで):

ignore                          Count: 1300     Time: 0.052
using getc                      Count: 1300     Time: 0.044
using fread                     Count: 1300     Time: 0.036
ignore                          Count: 1300     Time: 0.068
using streambuf iterators       Count: 1300     Time: 0.068
using stream iterators          Count: 1300     Time: 0.131
using istream::read             Count: 1300     Time: 0.037
using operator>>                Count: 1300     Time: 0.121

結果からわかるように、iostreamが非常に遅いということは実際には問題ではありません。むしろ、かなりの程度は、iostreamの使用方法に正確に依存します(そして程度は低いですがFILE *にも依存します)。また、これらと実装の間にはかなり大きな違いがあります。

それでも、それぞれの最速バージョン(freadistream::read)は基本的に同じです。 VC++を使用すると、getcistream::readまたはistreambuf_iteratorよりもかなり遅くなります。

結論:iostreamから良好なパフォーマンスを得るには、FILE *を使用する場合よりも少し注意が必要ですが、それは確かに可能です。これらはまた、より多くのオプションを提供します:速度をそれほど気にしない場合の利便性、およびわずかな追加作業で、CスタイルのI/Oから得られる最高のものと直接競合するパフォーマンス。

9
Jerry Coffin

この質問はかなり古いですが、iostreamオブジェクトの構築について誰も言及していません。

つまり、STL iostream(およびその他のストリームバリアント)を作成するたびに、コードにステップインすると、コンストラクターは内部Init関数を呼び出します。そこでoperator newが呼び出され、新しいlocaleオブジェクトが作成されます。同様に、破壊時に破壊されます。

これは恐ろしいです、私見。また、ある時点でシステムロックを使用してメモリが割り当てられたり割り当て解除されたりするため、オブジェクトの構築や破棄が遅くなる原因にもなります。

さらに、一部のSTLストリームではallocatorを指定できますが、なぜlocaleは指定されたアロケーターを使用せずに作成されるのですか?

マルチスレッド環境でストリームを使用すると、新しいストリームオブジェクトが構築されるたびにoperator newを呼び出すことによって課されるボトルネックを想像することもできます。

私が今自分自身を発見しているので、あなたが私に尋ねたら、恐ろしい混乱です!

1
dicksters
0
AndrewDover