std::fstream缓冲区与手动缓冲区(为什么手动缓冲区可以提高10倍性能)?
std::fstream缓冲区与手动缓冲区(为什么手动缓冲区可以提高10倍性能)?
我测试了两种写入配置:
- Fstream缓冲:
// 初始化 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) // 写入时使用: stream.write(reinterpret_cast
(&x), sizeof(x)); - 手动缓冲:
// 初始化 const unsigned int length = 8192; char buffer[length]; std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc); // 然后我手动将数据放入缓冲区 // 写入时使用: stream.write(buffer, length);
我原本期望结果是一样的...
但是手动缓冲提升了性能,写入一个100MB的文件速度提高了10倍,而fstream缓冲与正常情况(不重新定义缓冲区)相比没有改变。
有人能解释这种情况吗?
编辑:
以下是在超级计算机上进行的性能测试(Linux 64位架构,最新的英特尔Xeon 8核处理器,Lustre文件系统和...希望配置良好的编译器)
(我不解释1kB手动缓冲的"共鸣"原因...)
编辑2:
并且在1024B时出现共振(如果有人对此有想法,我很感兴趣):
std::fstream缓冲与手动缓冲(为什么手动缓冲可以获得10倍的增益)
这个问题的出现是由于函数调用开销和间接性引起的。ofstream::write()方法是从ostream继承而来的。在libstdc++中,该函数没有被内联,这是开销的第一个来源。然后ostream::write()必须调用rdbuf()->sputn()来进行实际的写入操作,这是一个虚函数调用。
除此之外,libstdc++将sputn()重定向到另一个虚函数xsputn(),这又增加了一次虚函数调用。
如果你自己将字符放入缓冲区,就可以避免这种开销。
我必须承认我不明白为什么这个答案是正确的。两个版本都使用了stream::write,为什么会有所不同?pubsetbuf然后打开应该和ifstream cstr有相同的效果。
:问题中没有明确说明,但假设在第一种情况下,当写入x
时,这些写入是小的,所以为了相同的数据量,你最终会进行更多次的stream.write
调用。
解决方法是手动缓冲。通过手动将字符放入缓冲区,可以避免函数调用开销和间接性引起的性能损失。这种方法可以通过以下步骤实现:
1. 创建一个字符数组作为缓冲区。
2. 使用字符数组的指针来写入字符数据。
3. 当缓冲区满时,使用write()方法将缓冲区中的数据写入文件。
4. 重复步骤2和3,直到所有字符数据都被写入文件。
通过手动缓冲,可以大大提高性能,因为避免了函数调用开销和间接性引起的性能损失。这是为什么手动缓冲可以获得10倍增益的原因。
下面是使用手动缓冲的示例代码:
#include
int main() {
std::ofstream file("data.txt", std::ios::binary);
const int bufferSize = 1024;
char buffer[bufferSize];
for (int i = 0; i < 1000000; i++) {
// Put characters into the buffer manually
buffer[i % bufferSize] = 'x';
// When the buffer is full, write it to the file
if ((i + 1) % bufferSize == 0) {
file.write(buffer, bufferSize);
}
}
file.close();
return 0;
}
通过手动缓冲,将字符放入缓冲区并一次性写入文件,可以避免频繁的函数调用和间接性引起的性能损失,从而获得更高的性能。
std::fstream有一个内置的缓冲区,它可以在每次写入数据时将其存储在缓冲区中,然后再将缓冲区中的内容一次性地写入磁盘。这是一种自动的缓冲机制,可以提高写入文件的效率。但是,有时手动进行缓冲操作可能会比std::fstream的自动缓冲机制更有效,可以获得更高的性能。
在这个问题中,原始代码每次只写入了少量的字节。由于std::fstream的自动缓冲机制,每次写入操作都会导致一次函数调用和一次数据拷贝,这会产生很大的开销。而手动进行缓冲操作可以避免这些开销,因此可以获得更高的性能。
解决这个问题的方法是使用手动缓冲机制。可以声明一个缓冲区,并将要写入的数据存储在缓冲区中。当缓冲区已满或需要刷新时,将缓冲区的内容一次性地写入磁盘。这样可以减少函数调用和数据拷贝的次数,提高写入文件的性能。
需要注意的是,如果要写入的数据量较大(大于等于1kB),使用std::fstream的自动缓冲机制和手动缓冲机制之间可能没有明显的性能差异。因为对于大块数据的写入,std::fstream的自动缓冲机制已经足够高效。
总结起来,std::fstream的自动缓冲机制在每次写入少量数据时可能会导致较大的性能开销。通过手动进行缓冲操作,可以减少函数调用和数据拷贝次数,提高写入文件的性能。但对于大块数据的写入,std::fstream的自动缓冲机制已经足够高效。
从上述内容中可以整理出以下问题的原因和解决方法:
问题:为什么手动缓冲大小小于1024字节时性能提升了10倍?
原因:问题与writev()和write()系统调用的高成本以及std::filebuf的内部实现有关。当使用小于1024的数据块写入时,至少会调用两次writev()系统调用,这导致了性能下降。而当使用大于等于1024字节的缓冲区一次性调用ofstream::write()时,每次调用都会触发一次writev()系统调用,这会导致性能提升。
解决方法:
1. 通过自定义类替换std::filebuf并重新定义std::ofstream。
2. 将要传递给ofstream::write()的缓冲区分成小于1024字节的片段,并逐个传递给ofstream::write()。
3. 避免将小数据片段传递给ofstream::write(),以避免在std::ofstream的虚拟函数上降低性能。