C ++-std :: fstream缓冲与手动缓冲(为什么手动缓冲获得10倍增益)?

我已经测试了两种书写配置:

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位体系结构,使用了Intel Xeon 8核,Lustre文件系统以及...配置良好的编译器)benchmark(并且我不解释1kB手动缓冲器“共振”的原因...)

编辑2:和在1024 B处的共振(如果有人对此有想法,我很感兴趣):enter image description here

Vincent asked 2019-11-18T15:10:03Z
3个解决方案
25 votes

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

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

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

Vaughn Cato answered 2019-11-18T15:10:40Z
6 votes

我想解释第二张图表中出现峰值的原因。

实际上,正如我们在第一张图片中所看到的,ofstream::write()使用的虚函数导致性能下降,但是并没有给出为什么最高性能是当手动缓冲区大小小于1024字节时的答案。

该问题与ofstream::write()writev()系统调用的高成本以及ofstream::write内部类ofstream::write()的内部实现有关。

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

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

如您所见,缓冲区越少,写速度就越低,ofstream::write()在系统空间中花费的时间也就更多。 因此,当缓冲区大小减小时,读/写速度会降低。

但是,为什么最快的速度是主题创建者手动缓冲区测试中的手动缓冲区大小小于1024字节时的原因? 为什么几乎恒定?

该说明涉及ofstream::write()实现,尤其是writev()

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

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

此外,如果您想使用std::ofstreamofstream::write()的缓冲区设置为大于1024的缓冲区(例如8192字节的缓冲区),并调用ostream::write()使用1024个大小的块来写入数据,那么您将感到惊讶,写入速度将与您将要的相同 使用1024缓冲区。 这是因为当传递的缓冲区大于或等于1024字节时,对std::ofstream的内部类ofstream::write()的实现进行了硬编码,以强制对每个ofstream::write()调用强制调用系统writev()调用(请参见basic_filebuf :: xsputn()源代码)。 2014年11月5日报告了GCC bugzilla中还有一个未解决的问题。

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

  • 用您自己的班级替换ofstream::write()并重新定义std::ofstream
  • 设计一个缓冲区,该缓冲区必须传递给ofstream::write(),小于1024的片段,然后逐个传递给std::ofstream
  • 不要将少量数据传递给ofstream::write(),以免降低std::ofstream的虚拟功能的性能
nomad85 answered 2019-11-18T15:12:43Z
0 votes

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

wolf1oo answered 2019-11-18T15:13:17Z
translate from https://stackoverflow.com:/questions/12997131/stdfstream-buffering-vs-manual-buffering-why-10x-gain-with-manual-buffering