C++ 中的文件写入性能不规则

Irregular file writing performance in c++

本文关键字:性能 不规则 文件 C++      更新时间:2023-10-16

我正在编写一个应用程序,该应用程序通过简单的函数调用接收二进制数据流,例如put(DataBLock, dateTime);,其中每个数据包为 4 MB

我必须将这些数据块写入单独的文件,以便将来使用一些额外的数据,如 id、插入时间、标签等......

所以我都尝试了这两种方法:

首先FILE

data.id = seedFileId; 
seedFileId++;
std::string fileName = getFileName(data.id);
char *fNameArray = (char*)fileName.c_str(); 
FILE* pFile;
pFile = fopen(fNameArray,"wb");
fwrite(reinterpret_cast<const char *>(&data.dataTime), 1, sizeof(data.dataTime), pFile);        
data.dataInsertionTime = time(0);
fwrite(reinterpret_cast<const char *>(&data.dataInsertionTime), 1, sizeof(data.dataInsertionTime), pFile);
fwrite(reinterpret_cast<const char *>(&data.id), 1, sizeof(long), pFile);
fwrite(reinterpret_cast<const char *>(&data.tag), 1, sizeof(data.tag), pFile);
fwrite(reinterpret_cast<const char *>(&data.data_block[0]), 1, data.data_block.size() * sizeof(int), pFile);
fclose(pFile);

第二个与ostream

ofstream fout;
data.id = seedFileId; 
seedFileId++;
std::string fileName = getFileName(data.id);
char *fNameArray = (char*)fileName.c_str(); 
fout.open(fNameArray, ios::out| ios::binary | ios::app);

fout.write(reinterpret_cast<const char *>(&data.dataTime), sizeof(data.dataTime));      
data.dataInsertionTime = time(0);
fout.write(reinterpret_cast<const char *>(&data.dataInsertionTime), sizeof(data.dataInsertionTime));
fout.write(reinterpret_cast<const char *>(&data.id), sizeof(long));
fout.write(reinterpret_cast<const char *>(&data.tag), sizeof(data.tag));
fout.write(reinterpret_cast<const char *>(&data.data_block[0]), data.data_block.size() * sizeof(int));
fout.close();

在我的测试中,第一种方法看起来更快,但我的主要问题是一开始一切都很好,对于每个文件写入操作,它几乎花费相同的时间(如 20 毫秒),但在第 250 - 300 个包之后,它开始产生一些峰值,如 150 到 300 毫秒,然后下降到 20 毫秒,然后再次下降到 150 毫秒,依此类推......所以它变得非常不可预测。

当我在代码中放置一些计时器时,我发现这些峰值的主要原因是因为fout.open(...)pfile = fopen(...)行。我不知道这是否是因为操作系统、硬盘驱动器、任何类型的缓存或缓冲机制等......

所以问题是;为什么这些文件打开行在一段时间后变得有问题,有没有办法使文件写入操作稳定,我的意思是固定时间?

谢谢。

注意:我使用的是Visual Studio 2008 vc++,Windows 7 x64。(我也尝试了32位配置,但结果是一样的)

编辑:经过一段时间后,即使打开文件时间最短,写入速度也会减慢。我尝试了不同的包装尺寸,所以结果如下:

对于 2 MB 的软件包,速度需要双倍的时间才能变慢,我的意思是之后 ~ 600 个项目开始减速

对于 4 MB 包,几乎第 300 个项目

对于 8 MB 包,几乎第 150 个项目

所以在我看来,这是某种缓存问题或其他什么?(在硬盘驱动器或操作系统中)。但是我也尝试禁用硬盘驱动器缓存,但没有任何变化...

知道吗?

这一切都是完全正常的,您正在观察文件系统缓存的行为。 这是操作系统留出的一块 RAM 来缓冲磁盘数据。 它通常是一个胖千兆字节,如果您的机器有很多 RAM,可能会更多。 听起来您已经安装了 4 GB,对于 64 位操作系统来说并不多。 但是,这取决于计算机上运行的其他进程的 RAM 需求。

您对 fwrite() 或 ofstream::write() 的调用会写入由 CRT 创建的小缓冲区,它反过来进行操作系统调用以刷新整个缓冲区。 操作系统通常完全非常快速地写入,它是从CRT缓冲区到文件系统缓存的简单内存到内存副本。 有效写入速度超过千兆字节/秒。

文件系统

驱动程序将文件系统缓存数据延迟写入磁盘。 经过优化,可最大限度地减少写入磁头上的寻道时间,这是迄今为止磁盘驱动器上最昂贵的操作。 有效写入速度由磁盘盘片的转速和放置写入磁头所需的时间决定。 对于消费级驱动器,典型值约为 30 兆字节/秒,给定或采用 2 倍。

也许你在这里看到了消防水带的问题。 写入文件缓存的速度比清空文件缓存的速度要快得多。 这最终确实碰壁了,您将设法将缓存填满,并突然看到程序的性能从悬崖上掉下来。 程序现在必须等到缓存中的空间打开才能完成写入,有效写入速度现在受到磁盘写入速度的限制。

您观察到的 20 毫秒延迟也是正常的。 这通常是打开文件所需的时间。 这是一个完全由磁盘头寻道时间主导的时间,它需要前往文件系统索引来写入目录条目。 标称时间在 20 到 50 毫秒之间,您已经处于低端。

显然,在代码中,您几乎无法对此进行改进。 正如您所发现的,您使用的CRT功能肯定没有任何区别。 充其量,您可以增加写入文件的大小,从而减少创建文件所花费的开销。

购买更多 RAM 始终是一个好主意。 但它当然只是延迟了消防水带溢出水桶的那一刻。 您需要更好的驱动器硬件才能取得成功。 SSD 非常好,条带 RAID 阵列也是如此。 最好的办法是不要等待程序完成:)

所以问题是;为什么这些文件打开行会变得有问题 一段时间后,有没有办法进行文件写入操作 稳定,我的意思是固定时间?

此观察结果(即写入操作所花费的不同时间)并不意味着操作系统或文件系统存在问题。您的观察背后可能有各种原因。一个可能的原因可能是内核可能使用延迟写入将数据写入磁盘。有时内核会缓存它(缓冲区),以防另一个进程很快读取或写入它,从而避免额外的磁盘操作。

这种情况可能会导致不同写入调用对相同大小的数据/缓冲区所花费的时间不一致。

文件 I/O 是一个有点复杂和复杂的主题,取决于各种其他因素。有关文件系统内部算法的完整信息,您可能需要参考 Maurice J Bach 的伟大经典著作"UNIX 操作系统的设计",其中详细描述了这些概念和实现。

话虽如此,您可能希望在程序的两个版本(即 C 和 C++)的写入调用后立即使用刷新调用。这样,您可以在文件 I/O 写入时间中获得一致的时间。否则,您的程序行为对我来说是正确的。

//C program
fwrite(data,fp);
fflush(fp);
//C++ Program
fout.write(data);
fout.flush();

峰值可能与 I/O 本身无关,而是与 NTFS 元数据有关:当您的文件计数达到某个限制时,一些类似 NTFS AVL 的数据结构需要一些重构和...... 颠簸!

要检查它,您应该预先分配文件条目,例如创建所有大小为零的文件,然后在编写时打开它们,仅用于测试:如果我的理论是正确的,您不应该再看到您的峰值了。

呃 - 你必须在那里禁用文件索引(Windows搜索服务)!只是想起来了...看这里。