繁体   English   中英

std::fstream 缓冲 vs 手动缓冲(为什么手动缓冲增益 10 倍)?

[英]std::fstream buffering vs manual buffering (why 10x gain with manual buffering)?

我测试了两种写入配置:

  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 处的共振(如果有人对此有想法,我很感兴趣):在此处输入图片说明

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

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

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

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

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

该问题与writev()write()系统调用的writev()以及std::ofstreamstd::filebuf内部类的内部实现有关。

为了展示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 的片段写入数据时,对于两个ofstream::write()操作(片段的大小为 1023 < 1024 - 首先是调用writev() (而不是write() )系统调用至少一次写入缓冲区,第二个强制写入第一个和第二个)。 基于它,我们可以得出结论ofstream::write()速度不依赖于峰值之前的手动缓冲区大小( write()很少被调用至少两次)。

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

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

因此,可以使用两种可能的情况来提供此问题的解决方案:

  • 用你自己的类替换std::filebuf并重新定义std::ofstream
  • 将必须传递给ofstream::write()的缓冲区分成大小小于 1024 的片段,然后将它们一一传递给ofstream::write()
  • 不要将小块数据传递给ofstream::write()以避免降低std::ofstream虚函数的性能

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM