用C++快速读写文件

Reading and writing files fast in C++

本文关键字:读写 文件 C++      更新时间:2023-10-16

我正试图将存储在文件中的几兆字节数据读写到我的SSD中,这些数据由每行转换为字符串的8个浮点组成。查找C++代码并在这里实现一些读写文件的答案,我得到了这个读文件的代码:

std::stringstream file;
std::fstream stream;
stream.open("file.txt", std::fstream::in);
file << stream.rdbuf();
stream.close(); 

和这个写文件的代码:

stream.write(file.str().data(), file.tellg());

问题是,与我的SSD的速度相比,这个代码非常慢。我的SSD具有2400 MB/s的读取速度和1800 MB/s的写入速度。但是我的程序只有180.6 MB/s的读取速度和25.11 MB/s的写入速度。

因为有人问我如何测量速度,我用std::chrono::steady_clock::now()得到了std::chrono::steady_clock::time_point,然后做了std::chrono::duration_cast。使用相同的5.6MB大文件,将文件大小除以测量的时间,我得到了每秒的兆字节数。

如何在只使用标准C++和STL的情况下提高对文件的读写速度

我为您做了一个简短的评估。

我写了一个测试程序,它首先创建一个测试文件。

然后我做了几个改进方法:

  1. 我打开所有编译器优化
  2. 对于字符串,我使用resize来避免重新分配
  3. 通过设置更大的输入缓冲区,从流中读取得到了显著改善

请查看并检查,如果你能为你的解决方案实现我的一个想法


编辑

将测试程序剥离为纯读取:

#include <string>
#include <iterator>
#include <iostream>
#include <fstream>
#include <chrono>
#include <algorithm>
constexpr size_t NumberOfExpectedBytes = 80'000'000;
constexpr size_t SizeOfIOStreamBuffer = 1'000'000;
static char ioBuffer[SizeOfIOStreamBuffer];
const std::string fileName{ "r:\log.txt" };
void writeTestFile() {
if (std::ofstream ofs(fileName); ofs) {
for (size_t i = 0; i < 2'000'000; ++i)
ofs << "text,text,text,text,text,text," << i << "n";
}
}

int main() {
//writeTestFile();
// Make string with big buffer
std::string completeFile{};
completeFile.resize(NumberOfExpectedBytes);
if (std::ifstream ifs(fileName); ifs) {
// Increase buffer size for buffered input
ifs.rdbuf()->pubsetbuf(ioBuffer, SizeOfIOStreamBuffer);
// Time measurement start
auto start = std::chrono::system_clock::now();
// Read complete file
std::copy(std::istreambuf_iterator<char>(ifs), {}, completeFile.begin());
// Time measurement evaluation
auto end = std::chrono::system_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// How long did it take?
std::cout << "Elapsed time:       " << elapsed.count() << " msn";
}
else std::cerr << "n*** Error.  Could not open source filen";
return 0;
}

有了它,我确实实现了123,2MB/s的

您可以尝试一次复制整个文件,看看这是否提高了速度:

#include <algorithm>
#include <fstream>
#include <iterator>
int main() {
std::ifstream is("infile");
std::ofstream os("outfile");
std::copy(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>{},
std::ostreambuf_iterator<char>(os));
// or simply: os << is.rdbuf()
}

在您的示例中,较慢的部分可能是对getline()的重复调用。虽然这在某种程度上取决于实现,但通常对getline的调用最终会归结为从打开的文件中检索下一行文本的操作系统调用。操作系统调用是昂贵的,应该避免在紧循环中调用。

考虑一个getline实现,它会产生大约1ms的开销。如果你调用它1000次,每次读取大约80个字符,你就获得了整整一秒的开销。另一方面,如果您调用它一次并读取80000个字符,那么您已经消除了999ms的开销,该函数可能会立即返回。

(这也是游戏等实现自定义内存管理的原因之一,而不仅仅是到处都是mallocnew。(

对于阅读:如果文件能放在内存中,请立即读取整个文件。

请参阅:如何在C++中将整个文件读取为std::字符串?

具体而言,请参见底部的slurp答案。(记住关于使用std::vector而不是char[]阵列的评论。(

如果它不能全部放在内存中,请将其分为大块进行管理。

对于编写:在stringstream或类似的缓冲区中构建输出,然后一步一步地或大块地编写,以最大限度地减少操作系统的往返次数。

看起来您正在将格式化的数字输出到文件中。已经存在两个瓶颈:将数字格式化为可读形式和文件I/O。

您所能达到的最佳性能是保持数据的流动性。起步和停车需要头顶罚款。

我建议使用两个或多个线程进行双重缓冲。

一个线程将数据格式化为一个或多个缓冲区。另一个线程将缓冲区写入文件。您需要调整缓冲区的大小和数量以保持数据的流动。当一个线程完成一个缓冲区时,该线程开始处理另一个缓冲区。例如,您可以让写入线程使用fstream.write()来写入整个缓冲区。

具有线程的双缓冲也可以适用于读取。一个线程将文件中的数据读取到一个或多个缓冲区中,另一个线程则将(缓冲区中的(数据格式化为内部格式。