重叠的IO或文件映射?

时间:2013-02-23 13:24:59

标签: winapi file-io overlapped-io file-mapping

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

2 个答案:

答案 0 :(得分:11)

您肯定希望使用内存映射文件。一些人多年来一直主张重叠IO(带有FILE_FLAG_NO_BUFFERING)作为“将数据送入RAM的最快方式”,但这只适用于具有非常特定条件的非常复杂的情况。在正常的平均情况下,关闭缓冲区缓存是一种严重的反优化。

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

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

重叠IO使用更多CPU,使用缓冲区缓存时速度慢得多,异步恢复到一些记录良好和一些未记录条件下的同步(例如加密,压缩和......纯粹机会?请求大小?请求数量?),在不可预测的时间停止你的申请 提交请求有时可能需要“有趣”的时间,而CancelIO有时不会取消任何内容,只能等待完成。具有未完成请求的进程是不可销毁的。管理具有出色重叠写入的缓冲区是非常重要的额外工作。

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

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

答案 1 :(得分:4)

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

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