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

时间:2012-10-21 10:45:25

标签: c++ file c++11 buffer fstream

我测试了两种写入配置:

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文件系统和...希望配置良好的编译器) benchmark (我没有解释1kB手动缓冲器“共振”的原因......)

编辑2: 而在1024 B的共振(如果有人对此有所了解,我很感兴趣): enter image description here

3 个答案:

答案 0 :(得分:24)

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

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

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

答案 1 :(得分:3)

我想解释second chart

中峰值的原因是什么

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

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

为了显示write()对性能的影响我在Linux机器上使用dd工具进行了一项简单测试,以复制具有不同缓冲区大小的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

正如您所看到的缓冲区越少,写入速度越慢,系统空间中花费的时间dd就越多。因此,当缓冲区大小减小时,读/写速度会降低。

但是为什么最高速度是在主题创建者手动缓冲区测试中手动缓冲区大小小于1024字节时?为什么它几乎不变?

解释与std::ofstream实施有关,尤其与std::basic_filebuf

有关

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

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

此外,如果您希望使用std::ofstreamstreambuf::pubsetbuf()缓冲区设置为大于1024的缓冲区(例如,8192字节缓冲区),并使用1024个大小的和平来调用ostream::write()来写入数据,你会惊讶地发现写入速度与使用1024缓冲区的速度相同。这是因为std::basic_filebuf 的实施 - 内部类std::ofstream - 硬编码强制调用系统writev()传递缓冲区时ofstream::write()调用大于或等于1024字节(请参阅basic_filebuf::xsputn()源代码)。 GCC bugzilla中还存在一个未解决的问题,该问题已在2014-11-05报告。

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

  • 用您自己的班级替换std::filebuf并重新定义std::ofstream
  • 将一个缓冲区(必须传递给ofstream::write())发送到小于1024的和平区,并将它们逐个传递给ofstream::write()
  • 不要将小小的数据传递给ofstream::write(),以避免降低std::ofstream
  • 虚拟功能的效果

答案 2 :(得分:0)

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