【问题标题】:Why is buffering in C++ important?为什么 C++ 中的缓冲很重要?
【发布时间】:2011-07-04 00:51:07
【问题描述】:

我尝试打印Hello World 200,000 次,但我花了很长时间,所以我不得不停下来。但是在我添加一个char 数组作为缓冲区之后,只用了不到 10 秒。为什么?

添加缓冲区之前:

#include <iostream> 
using namespace std;

int main() {
        int count = 0;
        std::ios_base::sync_with_stdio(false);
        for(int i = 1; i < 200000; i++)
        {       
                cout << "Hello world!\n";
                count++;
        }
                cout<<"Count:%d\n"<<count;
return 0;
}

这是在添加缓冲区之后:

#include <iostream> 
using namespace std;

int main() {
        int count = 0;
        std::ios_base::sync_with_stdio(false);
        char buffer[1024];
        cout.rdbuf()->pubsetbuf(buffer, 1024);
        for(int i = 1; i < 200000; i++)
        {       
                cout << "Hello world!\n";
                count++;
        }
                cout<<"Count:%d\n"<<count;
return 0;
}

这让我想到了 Java。使用 BufferReader 读取文件有什么好处?

【问题讨论】:

  • 基本上写一次 20 个元素比写一个元素 20 次要快。

标签: c++ buffer


【解决方案1】:

如果你有一个缓冲区,你会得到更少的实际 I/O 调用,这是缓慢的部分。首先,缓冲区被填满,然后进行一次 I/O 调用以刷新缓冲区。在 Java 或任何其他 I/O 速度较慢的系统中同样有用。

【讨论】:

  • 你的意思是标准输出流没有缓冲区?
【解决方案2】:

就文件操作而言,写入内存 (RAM) 总是比直接写入磁盘上的文件要快。

为了说明,让我们定义:

  • 对磁盘上文件的每次写入 IO 操作花费 1 毫秒
  • 通过网络对磁盘上的文件进行每次写入 IO 操作需要 5 毫秒
  • 对内存的每次写入 IO 操作花费 0.5 毫秒

假设我们必须将一些数据写入文件 100 次。

案例一:直接写入磁盘文件

100 times x 1 ms = 100 ms

案例2:通过网络直接写入磁盘上的文件

100 times x 5 ms = 500 ms

案例 3:在写入磁盘文件之前先在内存中缓冲

(100 times x 0.5 ms) + 1 ms = 51 ms

案例 4:在通过网络写入磁盘上的文件之前在内存中进行缓冲

(100 times x 0.5 ms) + 5 ms = 55 ms

结论

在内存中缓冲总是比直接操作快。但是,如果您的系统内存不足并且必须与页面文件交换,它会再次变慢。因此,您必须平衡内存和磁盘/网络之间的 IO 操作。

【讨论】:

  • 我明白了。所以,直接写入文件比使用缓冲一次完成要慢得多。但是,与将部分数据多次写入文件相比,一次将数据全部写入缓冲区是否会使其更长?我想一次写入所有数据的时间少于直接多次写入文件但每次写入一点数据的时间。
  • 抱歉,Amumu,我没有收到您的问题。介意改写吗?
  • 抱歉这里有点乱。所以一次写入数据 > 每次写入一点数据,因为它的 IO 调用不那么复杂。
  • 在我的第二个缓冲区示例中,它将填充直到缓冲区已满,然后输出到控制台,然后丢弃缓冲区中的旧数据以接收新数据。对吗?
  • 这就像开车从一个城市搬到另一个城市。如果您一次移动一个箱子,您将花费大量时间开车。您希望一次至少移动一车货物。随着每次调用的规模增加,起初性能和效率会显着提高。然后你到达一个收益递减点。这一点取决于很多因素,但通常在 2KB 左右。 “你好世界!”远小于 2KB。
【解决方案3】:

cout 函数包含许多隐藏和复杂的逻辑,一直到内核,因此您可以将文本写入屏幕,当您以这种方式使用缓冲区时,您实际上是在执行批处理请求而不是重复复杂的 I/O 调用。

【讨论】:

    【解决方案4】:

    您使用的是什么编译器/平台?我在这里没有看到显着差异(RedHat,gcc 4.1.2);两个程序都需要 5-6 秒才能完成(但“用户”时间约为 150 毫秒)。如果我将输出重定向到文件(通过 shell),总时间约为 300 毫秒(因此 6 秒的大部分时间都用于等待我的控制台赶上程序)。

    换句话说,默认情况下应该缓冲输出,所以我很好奇为什么你会看到如此巨大的加速。

    3 个切线相关的注释:

    1. 您的程序有一个错误,因为您只打印 199999 次而不是规定的 200000 次(以 i = 0 开头或以 i &lt;= 200000 结尾)
    2. 在输出 count 时,您将 printf 语法与 cout 语法混合在一起......解决这个问题的方法很明显。
    3. 禁用sync_with_stdio 会在输出到控制台时对我产生小幅加速(大约5%),但在重定向到文件时影响可以忽略不计。这是您在大多数情况下可能不需要的微优化(恕我直言)。

    【讨论】:

    • 我在 windows xp 上使用 gcc 在代码块中运行了两个代码示例。没有缓冲区的代码的执行时间为 27 毫秒,而有缓冲区的代码的执行时间为 17 毫秒。
    • 我在 Visual Studio 2010 Professional、Windows Server 2008 R2 上运行它。第一个没有缓冲的例子花了很长时间,但没有缓冲。
    【解决方案5】:

    写入磁盘的主要问题是写入时间不是字节数的线性函数,而是具有巨大常数的仿射函数。

    在计算方面,这意味着对于 IO,您具有良好的吞吐量(低于内存,但仍然非常好),但是延迟很差(比通常的网络好一点)。

    如果您查看 HDD 或 SSD 的评估文章,您会注意到读/写测试分为两类:

    • 随机读取的吞吐量
    • 连续读取的吞吐量

    后者通常明显大于前者。

    通常,操作系统和 IO 库应该为您抽象这个,但正如您所注意到的,如果您的例程是 IO 密集型的,您可能会通过增加缓冲区大小来获得收益。这是正常的,该库通常是为各种用途量身定制的,因此为普通应用程序提供了一个很好的中间地带。如果您的应用程序不是“平均”的,那么它的执行速度可能不会那么快。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多