重叠的 IO 或文件映射?

Posted

技术标签:

【中文标题】重叠的 IO 或文件映射?【英文标题】:Overlapped IO or file mapping? 【发布时间】:2013-02-09 01:33:09 【问题描述】:

在 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(文件映射) 但我似乎无法推断出如何进行性能权衡。

【问题讨论】:

哇哦!我有风滚草徽章。也许我应该添加 C++ 标签... 【参考方案1】:

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

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

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

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

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

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

【讨论】:

【参考方案2】:

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

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

【讨论】:

感谢您的意见;它符合我对这种情况的看法。我更喜欢内存映射方法,但我已经调试、测试过代码并且出售替代品需要性能改进。 @hatcat:如果您还没有使用它,您也可以在打开文件时尝试使用FILE_FLAG_SEQUENTIAL_SCAN。它可以提高重叠和映射 IO 的性能。

以上是关于重叠的 IO 或文件映射?的主要内容,如果未能解决你的问题,请参考以下文章

Java I/O 扩展

第十四周总结 Io之文件流

如何在 Erlang 中进行内存映射 IO?

Java NIO - 内存映射文件

存储映射IO

文件映射原理及实现