C++中真正的异步文件IO

ercv8c1e  于 2023-01-15  发布在  其他
关注(0)|答案(2)|浏览(204)

我有一个超快的M.2硬盘,它有多快?没关系,反正我也用不上这个速度,所以我才问这个问题。
我有一个应用程序,它需要大量的内存,以至于内存都放不下它。幸运的是,它不是一次就需要的,而是用来保存计算的中间结果。
不幸的是,应用程序不能足够快地写入和读取这些数据。我尝试使用多个读取器和写入器线程,但这只会使情况变得更糟(后来我读到这是因为this)。
所以我的问题是:有没有可能在C++中实现真正的异步文件IO,以充分利用那些宣称的每秒千兆字节数?如果有可能,那么如何实现(以跨平台的方式)?
如果您知道某个库,您也可以推荐一个能够胜任此类任务的库,因为我认为重新发明轮子是没有意义的。

编辑:

下面的代码展示了我如何在程序中执行文件IO。它不是来自前面提到的程序,因为它不会是最小的。这一个说明了问题。不要介意Windows.h。它只用于设置线程关联。在实际程序中我也设置了关联,所以这就是为什么我包含了它。

#include <fstream>
#include <thread>
#include <memory>
#include <string>

#include <Windows.h> // for SetThreadAffinityMask()

void stress_write(unsigned bytes, int num)
{
    std::ofstream out("temp" + std::to_string(num));
    for (unsigned i = 0; i < bytes; ++i)
    {
        out << char(i);
    }
}

void lock_thread(unsigned core_idx)
{
    SetThreadAffinityMask(GetCurrentThread(), 1LL << core_idx);
}

int main()
{
    std::ios_base::sync_with_stdio(false);
    lock_thread(0);

    auto worker_count = std::thread::hardware_concurrency() - 1;

    std::unique_ptr<std::thread[]> threads = std::make_unique<std::thread[]>(worker_count); // faster than std::vector

    for (int i = 0; i < worker_count; ++i)
    {
        threads[i] = std::thread(
            [](unsigned idx) {
                lock_thread(idx);
                stress_write(1'000'000'000, idx);
            },
            i + 1
        );
    }
    stress_write(1'000'000'000, 0);

    for (int i = 0; i < worker_count; ++i)
    {
        threads[i].join();
    }
}

正如你所看到的,它只是普通的老fstream。在我的机器上,这使用100%的CPU,但只有7 - 9%的磁盘(约190MB/s)。我想知道它是否可以增加。

u4dcyp6a

u4dcyp6a1#

最简单的事情得到(最多)10倍的速度是改变这一点:

void stress_write(unsigned bytes, int num)
{
  std::ofstream out("temp" + std::to_string(num));
  for (unsigned i = 0; i < bytes; ++i)
  {
    out << char(i);
  }
}

改为:

void stress_write(unsigned bytes, int num)
{
  constexpr auto chunk_size = (1u << 12u); // tune as needed
  std::ofstream out("temp" + std::to_string(num));
  for (unsigned chunk = 0; chunk < (bytes+chunk_size-1)/chunk_size; ++chunk)
  {
    char chunk_buff[chunk_size];
    auto count = (std::min)( bytes - chunk_size*chunk, chunk_size );
    for (unsigned j = 0; j < count; ++j)
    {
      unsigned i = j + chunk_size*chunk;
      chunk_buff[j] = char(i); // processing
    }
    out.write( chunk_buff, count );
  }
}

其中我们在发送到std ofstream之前将写入分组为最多4096字节。
流操作有许多令人讨厌的、编译器难以避免的虚拟调用,当您一次只写入少量字节时,这些虚拟调用会影响性能。
通过将数据分成更大的块,我们使得vtable查找足够少,以至于它们不再占主导地位。
有关原因的更多详细信息,请参见this SO post
为了获得最后一点性能,你可能不得不使用像boost. asio这样的东西,或者访问你的平台原始异步文件io库。
但是,当你工作在〈10%的驱动器带宽,而栏杆你的CPU,瞄准低挂水果第一。

nbnkbykc

nbnkbykc2#

分块I/O确实是这里最重要的优化,在大多数情况下应该足够了。然而,对关于 * 异步 * IO的确切问题的直接答案如下。
Boost::Asio在1.21.0版本中增加了对文件操作的支持。界面与Asio的其余部分相似。
首先,我们需要创建一个表示文件的对象。最常见的用例是使用random_access_filestream_file。在本示例代码中,流文件就足够了。
阅读是通过async_read_some完成的,但是通常的async_read helper函数可以用来一次读取特定数量的字节。
如果操作系统支持的话,这些操作确实会在后台运行,并且只占用很少的处理器时间,Windows和Linux都支持这一点。

相关问题