73

我测试了两种书写配置:

  1. Fstream 缓冲:

    // Initialization
    const unsigned int length = 8192;
    char buffer[length];
    std::ofstream stream;
    stream.rdbuf()->pubsetbuf(buffer, length);
    stream.open("test.dat", std::ios::binary | std::ios::trunc)
    
    // To write I use :
    stream.write(reinterpret_cast<char*>(&x), sizeof(x));
    
  2. 手动缓冲:

    // Initialization
    const unsigned int length = 8192;
    char buffer[length];
    std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc);
    
    // Then I put manually the data in the buffer
    
    // To write I use :
    stream.write(buffer, length);
    

我期待同样的结果......

但是我的手动缓冲将性能提高了 10 倍以写入 100MB 的文件,并且与正常情况相比,fstream 缓冲没有任何改变(没有重新定义缓冲区)。

有人对这种情况有解释吗?

编辑:这是新闻:刚刚在超级计算机上完成的基准测试(Linux 64 位架构,持续英特尔至强 8 核,Lustre 文件系统和......希望配置良好 基准 的编译器)(我不解释原因1kB 手动缓冲区的“共振”...)

编辑 2:以及 1024 B 处的共振(如果有人对此有想法,我很感兴趣): 在此处输入图像描述

4

3 回答 3

38

这基本上是由于函数调用开销和间接性造成的。ofstream::write() 方法继承自 ostream。该函数未内联在 libstdc++ 中,这是开销的第一个来源。然后 ostream::write() 必须调用 rdbuf()->sputn() 来进行实际的写入,这是一个虚函数调用。

最重要的是,libstdc++ 将 sputn() 重定向到另一个虚函数 xsputn(),它添加了另一个虚函数调用。

如果您自己将字符放入缓冲区,则可以避免这种开销。

于 2012-10-21T17:53:16.613 回答
11

我想解释一下第二张图表中峰值的原因是什么。

事实上,使用的虚函数std::ofstream会导致性能下降,类似于我们在第一张图片上看到的情况,但它没有给出为什么当手动缓冲区大小小于 1024 字节时性能最高的原因。

这个问题涉及到系统调用和内部类的内部writev()实现的高成本。write()std::filebufstd::ofstream

为了展示如何write()影响性能,我在我的 Linux 机器上使用该工具进行了一个简单的测试,dd以复制具有不同缓冲区大小(bs选项)的 10MB 文件:

test@test$ time dd if=/dev/zero of=zero bs=256 count=40000
40000+0 records in
40000+0 records out
10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s

real    0m2.370s
user    0m0.000s
sys     0m0.952s
test$test: time dd if=/dev/zero of=zero bs=512 count=20000
20000+0 records in
20000+0 records out
10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s

real    0m1.324s
user    0m0.000s
sys     0m0.476s

test@test: time dd if=/dev/zero of=zero bs=1024 count=10000
10000+0 records in
10000+0 records out
10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s

real    0m0.798s
user    0m0.008s
sys     0m0.236s

test@test: time dd if=/dev/zero of=zero bs=4096 count=2500
2500+0 records in
2500+0 records out
10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s

real    0m0.293s
user    0m0.000s
sys     0m0.064s

如您所见:缓冲区越小,写入速度越低,因此dd在系统空间中花费的时间越多。因此,当缓冲区大小减小时,读/写速度会降低。

但是为什么在主题创建者手动缓冲测试中手动缓冲大小小于 1024 字节时速度会达到峰值为什么它几乎是恒定的

解释与std::ofstream实现有关,尤其是与std::basic_filebuf.

默认情况下,它使用 1024 字节缓冲区(BUFSIZ 变量)。因此,当您使用小于 1024 的片段写入数据时,writev()(不是write())系统调用至少会为两个ofstream::write()操作调用一次(片段的大小为 1023 < 1024 - 第一个写入缓冲区,第二个强制写入第一个和第二)。基于此,我们可以得出结论,ofstream::write()速度不依赖于峰值之前的手动缓冲区大小(write()很少调用至少两次)。

ofstream::write()当您尝试使用call一次写入大于或等于 1024 字节的缓冲区时,writev()系统调用会为每个ofstream::write. 因此,您会看到当手动缓冲区大于 1024(峰值之后)时速度会增加。

此外,如果您想设置std::ofstream缓冲区大于 1024 缓冲区(例如,8192 字节缓冲区)使用streambuf::pubsetbuf()并调用ostream::write()以使用 1024 大小的块写入数据,您会惊讶于写入速度将与使用相同1024 缓冲区。这是因为- 内部类 - 的实现std::basic_filebufstd::ofstream是硬编码的,以在传递的缓冲区大于或等于 1024 字节时强制writev()为每个调用调用系统调用(参见basic_filebuf::xsputn()源代码)。在2014-11-05报告的 GCC bugzilla 中还有一个未解决的问题。ofstream::write()

因此,可以使用两种可能的情况来解决这个问题:

  • 替换std::filebuf为您自己的班级并重新定义std::ofstream
  • 将必须传递给 的缓冲区ofstream::write()划分为小于 1024 的块,并ofstream::write()逐个传递给
  • 不要将小块数据传递给ofstream::write()以避免降低虚拟功能的性能std::ofstream
于 2018-02-02T15:24:05.630 回答
2

我想添加到现有响应中,如果写入大块数据,这种性能行为(来自虚拟方法调用/间接的所有开销)通常不是问题。问题和这些先前的答案似乎被忽略了(尽管可能隐含地理解)是原始代码每次都写入少量字节。只是为了向其他人澄清:如果您正在编写大块数据(〜kB +),则没有理由期望手动缓冲与使用std::fstream's 缓冲有显着的性能差异。

于 2018-07-19T16:56:48.880 回答