41

http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly的 50:40, Andrei Alexandrescu 开玩笑说 istream 的效率/速度有多慢。

过去我遇到过一个问题,即 ostream 很慢而 fwrite 明显更快(运行一次主循环时减少了很多秒),但我不明白为什么也没有调查过。

是什么让 C++ 中的 istream 和 ostream 变慢?或者至少与同样满足需求的其他东西(如 fread/fget、fwrite)相比慢。

4

5 回答 5

52

实际上,IOStreams 不必很慢!但是,以合理的方式实施它们以使它们快速运行是一个问题。大多数标准 C++ 库似乎都不太重视实现 IOStreams。很久以前,当我的CXXRT仍在维护时,它的速度与 stdio 一样快——如果使用得当!

但是请注意,使用 IOStreams 布局的用户几乎没有性能陷阱。以下指南适用于所有 IOStream 实现,尤其适用于那些为快速而量身定制的实现:

  1. 使用std::cinstd::cout,等需要调用std::sync_with_stdio(false)!如果没有这个调用,任何使用标准流对象都需要与 C 的标准流同步。当然,在使用std::sync_with_stdio(false)时假设您不std::cinstdinstd::coutwithstdout等混合使用。
  2. 不要使用,因为它要求对std::endl任何缓冲区进行许多不必要的刷新。同样,不要设置std::ios_base::unitbufstd::flush不必要地使用。
  3. 在创建自己的流缓冲区时(好的,很少有用户这样做),确保他们确实使用了内部缓冲区!处理单个字符会跳过多个条件和一个virtual使其非常缓慢的函数。
于 2013-09-08T21:39:28.340 回答
15

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

  • 共享格式化状态:每个格式化输出操作都必须检查之前可能被 I/O 操纵器改变的所有格式化状态。由于这个原因,iostream 本质上比所有格式信息都是本地printf的 -like API 慢(尤其是像 Rust 或{fmt}这样的格式字符串编译,以避免解析开销)。

  • 不受控制地使用语言环境:所有格式都通过低效的语言环境层,即使您不希望这样做,例如在编写 JSON 文件时。请参阅N4412:iostreams 的缺点

  • 低效的代码生成:使用 iostream 格式化消息通常由多个函数调用组成,因为参数和 I/O 操纵器与消息的某些部分交错。例如,有三个函数调用(godbolt)在

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

    与等效调用中的一个(Godbolt )相比:printf

    printf("The answer is %d.\n", answer);
    
  • 额外的缓冲和同步。这可以以sync_with_stdio(false)与其他 I/O 设施的互操作性较差为代价来禁用。

于 2020-12-16T14:56:28.973 回答
11

也许这可以让您了解您正在处理的内容:

#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::reador 和istreambuf_iterator.

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

于 2013-09-08T22:54:03.337 回答
1

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

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

这是可怕的,恕我直言。并且肯定会导致缓慢的对象构造/销毁,因为在某些时候正在使用系统锁分配/释放内存。

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

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

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

于 2018-03-14T19:58:52.910 回答
0

在一个类似的话题上,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- c 库中的错误

于 2013-09-09T01:03:54.387 回答