3

在 Windows 应用程序中,我有一个包含文件名和缓冲区的类。您使用文件名构造它,您可以查询对象以查看缓冲区是否已填充,如果没有则返回 nullptr,如果是则返回缓冲区地址。当对象超出范围时,释放缓冲区:

class file_buffer
{
public:
    file_buffer(const std::string& file_name);
    ~file_buffer();
    void* buffer();

private:
    ...
}

我想将数据异步放入内存,据我所知,我有两个选择:要么创建缓冲区并通过 ReadFileEx 使用重叠 IO,要么使用 MapViewOfFile 并在另一个线程上触摸地址。

目前我正在使用 ReadFileEx 这会出现一些问题,因为大于约 16MB 的请求很容易失败:我可以尝试拆分请求,但随后会出现同步问题,并且如果对象在 IO 之前超出范围完成我有缓冲区清理问题。此外,如果类的多个实例被快速连续创建,事情会变得非常复杂。

在另一个线程上映射和触摸数据似乎要容易得多,因为我不会遇到上限问题:如果客户端现在绝对必须拥有数据,他们可以简单地取消引用地址,让操作系统担心页面错误并接受阻塞命中。

这个应用程序需要支持单核机器,所以我的问题是:另一个软件线程上的页面错误会比当前线程上的重叠 IO 更昂贵吗?他们会拖延这个过程吗?重叠的 IO 是否会以相同的方式停止进程,还是有一些我不明白的操作系统魔法?是否仍然使用重叠 IO 执行页面错误?

我已经很好地阅读了这些主题: http: //msdn.microsoft.com/en-us/library/aa365199 (v=vs.85).aspx(文件管理中的 IO 概念) http://msdn。 microsoft.com/en-us/library/windows/desktop/aa366556(v=vs.85).aspx(文件映射),但我似乎无法推断如何进行性能权衡。

4

2 回答 2

14

您肯定会想要使用内存映射文件。Overlapped IO (with FILE_FLAG_NO_BUFFERING) 多年来一直被一些人提倡为“将数据放入 RAM 的最快方式”,但这仅在非常人为的情况下和非常具体的条件下才是正确的。在正常情况下,关闭缓冲区缓存是一种严重的反优化。

现在,没有 FILE_FLAG_NO_BUFFERING重叠 IO 的重叠 IO具有重叠 IO 的所有怪癖,并且速度慢了大约 50%(出于我仍然无法理解的原因)。

一年前我做了一些相当广泛的基准测试。底线是:内存映射文件更快、更好、更少令人惊讶。

重叠 IO 使用更多 CPU,使用缓冲区缓存时速度要慢得多,在一些有据可查和一些无证条件下(例如加密、压缩和......纯机会?请求大小?请求数?),异步恢复为同步,在不可预测的时间停止您的应用程序。
提交请求有时会花费“有趣”的时间,CancelIO有时不会取消任何内容,而是等待完成。具有未完成请求的进程是无法杀死的。管理具有突出重叠写入的缓冲区是非常重要的额外工作。

文件映射只是工作。句号。而且效果很好。没有惊喜,没有有趣的东西。接触每一页的开销非常小,并且交付速度与磁盘能够交付的速度一样快,并且它利用了缓冲区缓存。您对单核 CPU 的关注没有问题。如果触摸线程发生故障,它会阻塞,并且与往常一样,当一个线程阻塞时,另一个线程会获得 CPU 时间。

我什至现在使用文件映射进行写入,只要我有多个字节要写入。这有点不重要(必须手动增长/预分配文件和映射,并在关闭时截断为实际长度),但对于一些辅助类,它是完全可行的。写入 500 MiB 的数据,它需要“零时间”(你基本上是在后台执行memcpy,实际的写入发生在后台,以后的任何时间,甚至在你的程序完成之后)。即使您知道这是操作系统自然而然会做的事情,它的效果也令人惊叹。
当然,在操作系统写出所有页面之前最好不要出现电源故障,但对于任何类型的写入都是如此。不在磁盘上的东西不在磁盘上——除此之外,真的没有什么好说的了。如果您必须确定这一点,则必须等待磁盘同步完成,即使这样,您也不能确定等待同步时灯不会熄灭。这就是生活。

于 2013-03-04T12:03:44.667 回答
4

我并没有声称比您更了解这一点,因为您似乎做了一些发明。并且完全确定您需要进行实验。但这是我对这些问题的理解,顺序相反:

  1. Windows 中的文件映射和重叠 IO 是不同的实现方式,它们都不依赖于其他。但两者都使用异步块设备层。正如我想象的那样,在内核中,每个 IO 实际上都是异步的,但是一些用户操作会等待它完成,因此它们会产生同步的错觉。
  2. 从第 1 点开始,如果一个线程进行 IO,来自同一进程的其他线程将不会停顿。除非系统资源稀缺或这些其他线程自己做 IO 并面临某种争用。无论第一个线程执行哪种 IO,这都是正确的:阻塞、非阻塞、重叠、内存映射。
  3. 在内存映射文件中,一次至少读取一页数据,可能更多是因为预读,但您不能确定这一点。所以探测线程必须在每一页上至少接触一个映射内存。这将类似于probe/block-probe-probe-probe-probe/block-probe ...这可能比几MB的大重叠读取效率低一些。或者也许内核程序员很聪明,而且效率更高。您将不得不进行一些分析...嘿,您甚至可以不使用探测线程,看看会发生什么。
  4. 取消重叠操作是一个 PITA,所以我的建议是使用内存映射文件。这样设置起来更容易,并且您可以获得额外的功能:
    1. 内存甚至在它完全在内存中之前就可以使用
    2. 内存可以/将由进程的多个实例共享
    3. 如果内存在缓存中,它将立即准备好,而不是很快。
    4. 如果数据是只读的,您可以保护内存不被写入,捕捉错误。
于 2013-03-04T10:01:10.407 回答