关于C++:为什么istream/ostream 慢

Why is istream/ostream slow

在http://channel9.msdn.com/events/goingnative/2013/writing-quick-code-in-cpp-quickly Andrei Alexandrescu的50:40,他开玩笑说Istream有多不高效/慢。

我以前遇到过这样一个问题:Ostream速度慢,fwrite速度快得多(运行一次主循环时缩短了很多秒),但我不明白原因,也不去研究它。

是什么使得ISTRAM和OFFASE在C++中缓慢?或者至少比其他同样能满足需求的东西(如fread/fget、fwrite)要慢。


实际上,iostreams不必慢!不过,这是一个以合理的方式实现它们以使其快速的问题。大多数标准C++库似乎不太重视实现IoFiels.很久以前,当我的cxrt还在维护的时候,它和stdio一样快——正确使用的时候!

但是,请注意,对于使用iostreams部署的用户,性能陷阱很少。以下指导原则适用于所有iostream实现,尤其适用于那些为快速而定制的实现:

  • 使用std::cinstd::cout等时,需要打std::sync_with_stdio(false)电话!如果没有此调用,则需要使用标准流对象来与C的标准流同步。当然,在使用std::sync_with_stdio(false)时,假设不将std::cinstdin混合,std::coutstdout混合等。
  • 不要使用std::endl,因为它要求对任何缓冲区进行许多不必要的刷新。同样,不要不必要地设置std::ios_base::unitbuf或使用std::flush
  • 在创建自己的流缓冲区时(好的,很少有用户这样做),请确保他们确实使用内部缓冲区!处理单个字符会跳过多个条件和一个virtual函数,这会使处理速度非常慢。

  • 也许这可以让你对你正在处理的事情有所了解:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    #include <stdio.h>
    #include <iomanip>
    #include <iostream>
    #include <iterator>
    #include <fstream>
    #include <time.h>
    #include <string>
    #include

    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 <<"
    "
    ;
    }

    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++):

    1
    2
    3
    4
    5
    6
    7
    8
    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

    这个(和明格一起):

    1
    2
    3
    4
    5
    6
    7
    8
    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

    正如我们在结果中看到的那样,IOstreams绝对缓慢并不是问题。更确切地说,很大程度上取决于您如何使用iostreams(在较小程度上也取决于FILE *)。这些与实现之间也存在相当大的差异。

    尽管如此,每个版本的最快版本(freadistream::read基本上是捆绑的。使用vc++getcistream::readistreambuf_iterator慢得多。

    底线:从iostreams获得良好的性能需要比使用FILE *稍微谨慎一点,但这当然是可能的。它们还为您提供了更多的选择:当您不太关心速度时的便利性,以及与C风格I/O所能获得的最佳性能直接竞争的性能,只需要一点额外的工作。


    虽然这个问题很古老,但我很惊讶没有人提到iostream对象构造。

    也就是说,每当您创建一个stl iostream和其他流变体时,如果您单步执行代码,构造函数就会调用一个内部Init函数。在这里,operator new被调用来创建一个新的locale对象。同样地,一经毁灭就毁灭。

    这太可怕了,imho。当然,这会导致对象构造/破坏的速度变慢,因为在某些时候,内存是使用系统锁分配/释放的。

    此外,一些STL流允许您指定一个allocator,那么为什么创建的locale不使用指定的分配器?

    在多线程环境中使用流,还可以想象每次构造新的流对象时调用operator new所带来的瓶颈。

    如果你问我的话,那真是一团糟,因为我现在正发现自己呢!


    在类似的主题中,stl说:"可以调用setvbuf()在stdout上启用缓冲。"

    https://web.archive.org/web/20170329163751/https://connect.microsoft.com/VisualStudio/Feedback/Details/642876/std-wcout-is-ten-times-slower-than-wprintf-performance-bug-in-c-library