Improving/optimizing C++ 中的文件写入速度

Improving/optimizing file write speed in C++

我一直 运行 遇到一些写入文件的问题 - 即写入速度不够快。

解释一下,我的目标是捕获通过千兆以太网传入的数据流,并将其简单地保存到文件中。

原始数据以 10MS/s 的速率传入,然后保存到缓冲区并随后写入文件。

下面是代码的相关部分:

    std::string path = "Stream/raw.dat";
    ofstream outFile(path, ios::out | ios::app| ios::binary);

    if(outFile.is_open())
        cout << "Yes" << endl;

    while(1)
    {
         rxSamples = rxStream->recv(&rxBuffer[0], rxBuffer.size(), metaData);
         switch(metaData.error_code)
         {

             //Irrelevant error checking...

             //Write data to a file
                std::copy(begin(rxBuffer), end(rxBuffer), std::ostream_iterator<complex<float>>(outFile));
         }
    } 

我遇到的问题是将样本写入文件的时间太长。大约一秒钟后,发送样本的设备报告其缓冲区已溢出。在对代码进行一些快速分析后,几乎所有的执行时间都花在了 std::copy(...) 上(准确地说是 99.96% 的时间)。如果我删除这一行,我可以 运行 几个小时的程序而不会遇到任何溢出。

也就是说,我对如何提高写入速度感到很困惑。我浏览了该站点上的几篇文章,似乎最常见的建议(关于速度)是像我已经完成的那样通过使用 std::copy 来实现文件写入。

如果有帮助,我会 运行在 Ubuntu x86_64 上安装此程序。如有任何建议,我们将不胜感激。

所以这里的主要问题是你试图在接收的同一个线程中写入,这意味着你的recv()只能在复制完成后再次调用。几点观察:

  • 将写作移到另一个线程。这是关于 USRP 的,因此 GNU Radio 可能真的是您选择的工具 -- 它本质上是多线程的。
  • 您的输出迭代器可能不是最高效的解决方案。简单地 "write()" 到一个文件描述符可能会更好,但这是由你决定的性能测量
  • 如果你的硬 drive/file system/OS/CPU 达不到来自 USRP 的速率,即使将接收与线程写入分离,那么你也无能为力 -- 得到更快的系统。
  • 尝试写入 RAM 磁盘

事实上,我不知道您是如何想出 std::copy 方法的。 rx_samples_to_file example that comes with UHD 通过简单的写入来做到这一点,你绝对应该更喜欢它而不是复制;文件 I/O 在良好的操作系统上通常可以少一份副本,并且遍历所有元素可能非常慢。

让我们做一些数学运算。

您的样本(显然)属于 std::complex<std::float> 类型。给定一个(典型的)32 位浮点数,这意味着每个样本都是 64 位。在 10 MS/s,这意味着原始数据约为每秒 80 兆字节——这在您可以预期写入桌面 (7200 RPM) 硬盘驱动器的范围内,但已经相当接近限制(通常是大约每秒 100-100 兆字节左右)。

不幸的是,尽管 std::ios::binary,您实际上是以文本格式写入数据(因为 std::ostream_iterator 基本上 stream << data;)。

这不仅会损失一些精度,而且会增加数据的大小,至少通常是这样。确切的增加量取决于数据——一个小的整数值实际上可以减少数据量,但对于任意输入,接近 2:1 的大小增加是相当常见的。随着 2:1 的增加,您的传出数据现在约为 160 megabytes/second--which 比大多数硬盘驱动器可以处理的速度更快。

明显的改进起点是以二进制格式写入数据:

uint32_t nItems = std::end(rxBuffer)-std::begin(rxBuffer);
outFile.write((char *)&nItems, sizeof(nItems));
outFile.write((char *)&rxBuffer[0], sizeof(rxBuffer));

目前我使用 sizeof(rxBuffer) 假设它是一个真正的数组。如果它实际上是一个指针或向量,你必须计算正确的大小(你想要的是要写入的字节总数)。

我还注意到,就目前情况而言,您的代码有一个更严重的问题:由于它在写入数据时没有指定元素之间的分隔符,因此写入数据时将没有任何内容将一个项目与下一个项目分开。这意味着如果您写入两个值(例如)10.2,您读回的将不是 10.2,而是单个值10.2。将分隔符添加到您的文本输出将增加更多的开销(大约多 15% 的数据)到一个已经失败的进程,因为它生成了太多的数据。

以二进制格式写入意味着每个浮点数将恰好占用 4 个字节,因此无需分隔符即可正确读回数据。

之后的下一步将是下降到较低级别的文件 I/O 例程。根据情况,这可能会或可能不会有太大的不同。在 Windows 上,您可以在使用 CreateFile 打开文件时指定 FILE_FLAG_NO_BUFFERING。这意味着对该文件的读写基本上会绕过缓存,直接进入磁盘。

在您的情况下,这可能是一个胜利——在 10 MS/s,您可能会在重新读取相同数据之前用完缓存 space 相当长的一段时间。在这种情况下,让数据进入缓存几乎没有任何好处,但会花费一些数据将数据复制到缓存,然后再将其复制到磁盘。更糟糕的是,它可能会用所有这些数据污染缓存,因此它不再存储更有可能从缓存中受益的其他数据。