如何在 C++ 中同时写入文件(换句话说,写入文件的最快方法是啥)

Posted

技术标签:

【中文标题】如何在 C++ 中同时写入文件(换句话说,写入文件的最快方法是啥)【英文标题】:How to concurrently write to a file in c++(in other words, whats the fastest way to write to a file)如何在 C++ 中同时写入文件(换句话说,写入文件的最快方法是什么) 【发布时间】:2021-06-07 12:38:25 【问题描述】:

我正在构建一个图形引擎,我需要将结果图像写入 .bmp 文件。我将像素存储在vector<Color> 中。同时还节省了图像的宽度和高度。目前我正在编写如下图像(我自己没有编写此代码):

std::ostream &img::operator<<(std::ostream &out, EasyImage const &image) 

//temporaryily enable exceptions on output stream
enable_exceptions(out, std::ios::badbit | std::ios::failbit);
//declare some struct-vars we're going to need:
bmpfile_magic magic;
bmpfile_header file_header;
bmp_header header;
uint8_t padding[] =
        0, 0, 0, 0;
//calculate the total size of the pixel data
unsigned int line_width = image.get_width() * 3; //3 bytes per pixel
unsigned int line_padding = 0;
if (line_width % 4 != 0) 
    line_padding = 4 - (line_width % 4);

//lines must be aligned to a multiple of 4 bytes
line_width += line_padding;
unsigned int pixel_size = image.get_height() * line_width;

//start filling the headers
magic.magic[0] = 'B';
magic.magic[1] = 'M';

file_header.file_size = to_little_endian(pixel_size + sizeof(file_header) + sizeof(header) + sizeof(magic));
file_header.bmp_offset = to_little_endian(sizeof(file_header) + sizeof(header) + sizeof(magic));
file_header.reserved_1 = 0;
file_header.reserved_2 = 0;
header.header_size = to_little_endian(sizeof(header));
header.width = to_little_endian(image.get_width());
header.height = to_little_endian(image.get_height());
header.nplanes = to_little_endian(1);
header.bits_per_pixel = to_little_endian(24);//3bytes or 24 bits per pixel
header.compress_type = 0; //no compression
header.pixel_size = pixel_size;
header.hres = to_little_endian(11811); //11811 pixels/meter or 300dpi
header.vres = to_little_endian(11811); //11811 pixels/meter or 300dpi
header.ncolors = 0; //no color palette
header.nimpcolors = 0;//no important colors

//okay that should be all the header stuff: let's write it to the stream
out.write((char *) &magic, sizeof(magic));
out.write((char *) &file_header, sizeof(file_header));
out.write((char *) &header, sizeof(header));

//okay let's write the pixels themselves:
//they are arranged left->right, bottom->top, b,g,r
// this is the main bottleneck
for (unsigned int i = 0; i < image.get_height(); i++) 
    //loop over all lines
    for (unsigned int j = 0; j < image.get_width(); j++) 
        //loop over all pixels in a line
        //we cast &color to char*. since the color fields are ordered blue,green,red they should be written automatically
        //in the right order
        out.write((char *) &image(j, i), 3 * sizeof(uint8_t));
    
    if (line_padding > 0)
        out.write((char *) padding, line_padding);

//okay we should be done
return out;

如您所见,像素被一一写入。这很慢,我在我的程序中放了一些计时器,发现写作是我的主要瓶颈。

我试图写整个(水平)行,但我没有找到如何去做(我发现最好的是this。

其次,我想使用多线程写入文件(不确定是否需要使用线程或处理)。使用 openMP。但这意味着我需要指定写入哪个字节地址,我认为这是我无法解决的。

最后,我考虑在绘制对象时立即写入文件,但后来写入文件中的特定位置时遇到了同样的问题。

所以,我的问题是:解决这个问题的最佳(最快)方法是什么。 (为 windows 和 linux 编译这个)

【问题讨论】:

如果您将实际内存中的图像数据视为一个字节数组,如果使用 BMP 标头信息写入磁盘,它们的顺序是否正确?然后,您可以使用单个 write 调用一次性编写它。不过要小心行填充之类的东西。因为当前代码处理了这个问题,而您的内存数据可能没有它。也许改为写行? 多线程对计算很有用,但对磁盘 I/O 没有帮助。写入文件的最快方法是按顺序写入大块,例如 4 MB。 当我读取生成的 .bmp 文件的二进制数据并使用 python 打印时,我得到以下信息:´x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00\x00\x00\x00\.....´(图像大部分是黑色的)所以我可以尝试把它全部写一遍(像 rustyx 所说的那样,以 4 MB 的块为单位,或逐行写。如何我可以逐行写吗? 也许最简单的方法是只保留您当前拥有的代码,而是在不影响主事件或渲染线程的后台线程中运行整个函数?如果图像未在其他任何地方使用,只需像当前一样传递引用,否则创建它的内存副本(按值传递图像对象)。除非您需要保存应该足够好的每一帧。 如果您真的在寻找最快的写入性能,您可能希望使用操作系统特定的 API 调用,这些调用可以绕过操作系统完成的正常缓存并直接与磁盘硬件交互(但由于内存对齐限制而增加了复杂性)。 【参考方案1】:

写入文件的最快方法是使用硬件辅助。将输出写入内存(也称为缓冲区),然后告诉硬件设备从内存传输到文件(磁盘)。

下一个最快的方法是将所有数据写入缓冲区,然后将数据块写入文件。如果您希望在写入期间执行其他任务或线程,请创建一个线程将缓冲区写入文件。

写入文件时,每个事务的数据越多,写入的效率就越高。例如,1 次写入 1024 字节比写入 1024 次 1 字节快。

这个想法是保持数据流。降低传输速率可能比突发写入、延迟、突发写入、延迟等更快。

请记住,磁盘本质上是一个串行设备(除非您有特殊的硬盘驱动器)。使用比特流将比特放置在盘片上。并行写入数据会产生不利影响,因为头部必须在并行活动之间移动。

请记住,如果您使用多个内核,则数据总线上的流量会更多。当其他线程/任务正在使用数据总线时,到文件的传输将不得不暂停。因此,如果可以,请阻止所有任务,然后传输您的数据。 :-)

我编写了从慢速内存复制到快速内存,然后从快速内存传输到硬盘驱动器的程序。那也是使用中断(线程)。

总结 快速写入文件涉及:

    保持数据流;尽量减少停顿。 以二进制模式编写(请不要翻译)。 写入块(在写入块之前根据需要格式化到内存中)。 最大化事务中的数据。 如果您希望其他任务“同时”运行,请使用单独的写入线程。 硬盘是串行设备,不是并行的。位以串行流的形式写入盘片。

【讨论】:

您能否详细说明“硬件辅助”,您的意思是什么以及如何实施? 有一些设备,称为 DMA(直接内存访问),旨在将数据从内存移动到其他地方。它们可以是独立的,也可以是更大硬件设备的一部分。 USB 设备具有通过 USB 端口从内存中移动数据的能力。 PC 主板上可能有一些东西通过 SATA 通道将数据从内存移动到硬盘驱动器。基本上,这些硬件设备在后台运行,允许CPU在数据传输期间执行其他任务。 模式?糟糕的SSD。还有很多现代 io 是异步的,没有线程。 @Yakk-AdamNevraumont:线程仍在使用,尤其是在 GUI 应用程序中。您不希望 GUI 应用程序在数据写入硬盘时冻结。

以上是关于如何在 C++ 中同时写入文件(换句话说,写入文件的最快方法是啥)的主要内容,如果未能解决你的问题,请参考以下文章

避免同时写入 NFS 共享文本文件

C++ 如何将一个文件里的数据写入到另一个文件里?

C++。如何从函数写入文件

如何使用 C++ 代码中的 BTRFS 写入时复制?

如何使用 C++ 在 .txt 文件中写入、读取和重写

如何使用 C++ 写入文件的特定列?