c++ 为什么istream/ostream很慢

56lgkhnf  于 2023-06-25  发布在  其他
关注(0)|答案(5)|浏览(259)

在50:40的http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly安德烈Alexandrescu开了一个玩笑,说istream是多么的不高效/慢。
我过去有一个问题,ostream很慢,fwrite明显更快(运行主循环一次会减少很多秒),但我从来不明白为什么,也不去研究它。
是什么让istream和ostream在C++中变慢?或者至少与同样满足需求的其他东西(如fread/fget、fwrite)相比慢。

xyhw6mcr

xyhw6mcr1#

事实上,IOStreams并不需要很慢!不过,这是一个以合理的方式实施它们的问题,使它们快速。大多数标准的C++库似乎都没有太多的注意力来实现IOStreams。很久以前,当我的CXXRT还在维护的时候,它大约和stdio一样快-当正确使用的时候!
但是,请注意,对于使用IOStreams布局的用户来说,几乎没有性能陷阱。以下指导原则适用于所有IOStream实现,尤其适用于那些为快速而定制的实现:
1.使用std::cinstd::cout等时你需要调用std::sync_with_stdio(false)!如果没有这个调用,任何标准流对象的使用都需要与C的标准流同步。当然,当使用std::sync_with_stdio(false)时,假设您不会将std::cinstdinstd::coutstdout混合使用,等等。

  1. Do not use std::endl,因为它要求对任何缓冲器进行许多不必要的刷新。同样,不要设置std::ios_base::unitbuf或使用std::flush
    1.当创建自己的流缓冲区时(好吧,很少有用户这样做),请确保他们使用内部缓冲区!处理单个字符要经过多个条件和virtual函数,这使得它慢得可怕。
ie3xauqp

ie3xauqp2#

[i]ostreams在设计上很慢的原因有几个:

*共享格式化状态:每个格式化输出操作必须检查可能先前已经由I/O操纵器改变的所有格式化状态。由于这个原因,iostreams本质上比类似printf的API慢(特别是使用格式字符串编译,如Rust或{fmt},避免解析开销),其中所有格式信息都是本地的。
*不受控制地使用语言环境:所有的格式化都要经过一个低效的区域设置层,即使你不想这样,例如在编写JSON文件时。参见N4412: Shortcomings of iostreams
*codegen无效:使用iostreams格式化消息通常由多个格式化函数调用组成,因为参数和I/O操纵器与消息的各个部分交错。例如,在中有三个这样的调用(godbolt

std::cout << "The answer is " << answer << ".\n";

相比之下,在等价的printf调用中只有一个(godbolt):

printf("The answer is %d.\n", answer);

*额外的缓冲和同步sync_with_stdio(false)可以禁用这一点,但代价是与其他I/O设施的互操作性较差。

i2loujxw

i2loujxw3#

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

#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绝对慢的问题。相反,这在很大程度上取决于您如何使用iostreams(在较小程度上也取决于FILE *)。在这些实现之间也有相当大的差异。
尽管如此,每个版本(freadistream::read)的最快版本基本上是并列的。在VC ++中,getcistream::readistreambuf_iterator慢很多。
底线是:从iostreams获得良好的性能需要比使用FILE *多一点的注意--但这肯定是可能的。它还为您提供了更多的选择:当你不太关心速度时,你会发现它很方便,而且性能直接与你从C风格I/O中获得的最佳性能竞争,只需要一点额外的工作。

q5iwbnjs

q5iwbnjs4#

虽然这个问题很老了,但我很惊讶没有人提到iostream对象构造。
也就是说,每当创建STL iostream(和其他流变体)时,如果单步执行代码,构造函数将调用内部Init函数。在这里,调用operator new来创建一个新的locale对象。同样的,也会被毁灭。
这太可怕了,IMHO。这当然会导致对象构造/销毁速度变慢,因为内存在某个时候是使用系统锁来分配/释放的。
此外,一些STL流允许您指定allocator,那么为什么不使用指定的分配器创建locale呢?
在多线程环境中使用流,还可以想象每次构造新的流对象时调用operator new所造成的瓶颈。
如果你问我的话,这是可怕的混乱,因为我现在发现自己!

相关问题