(道歉有点冗长介绍)
在开发一个将整个大文件(> 400MB)预先默认为缓冲区caching以加快实际运行速度的应用程序的开发过程中,我testing了一次读取4MB是否比一次只读取1MB块有什么明显的好处。 令人惊讶的是,较小的请求实际上变得更快。 这似乎是反直觉的,所以我进行了更广泛的testing。
缓冲区caching在运行testing之前已被清除(只是为了笑,我也是在缓冲区中运行了一个文件)缓冲区caching提供了2GB / s以上的速度,无论请求大小如何,虽然有惊人的+/- 30%随机方差)。
所有使用重叠的ReadFile与相同的目标缓冲区(使用FILE_FLAG_OVERLAPPED
而不使用FILE_FLAG_OVERLAPPED
打开句柄)。 使用的硬盘有点年迈但function齐全,NTFS的簇大小为8kB。 磁盘在初始运行后进行了碎片整理(6个碎片与未碎片,零差异)。 为了更好的数字,我也使用了一个更大的文件,下面的数字是阅读1GB。
结果真是令人惊讶:
4MB x 256 : 5ms per request, completion 25.8s @ ~40 MB/s 1MB x 1024 : 11.7ms per request, completion 23.3s @ ~43 MB/s 32kB x 32768 : 12.6ms per request, completion 15.5s @ ~66 MB/s 16kB x 65536 : 12.8ms per request, completion 13.5s @ ~75 MB/s
因此,这表明提交一万个请求两个集群的长度实际上比提交几百个大的连续读取要好。 提交时间(ReadFile返回之前的时间)确实随着请求数量的增加而大幅增加,但asynchronous完成时间几乎减半。
在asynchronous读取完成时,内核CPU时间约为5-6%(在一个quadcore中,所以应该是20-30%),这是一个令人惊讶的CPU数量 – 显然,也是忙碌的等待的一小部分。 30%的CPU在2.6GHz下25秒,这是相当于几个“无”的周期。
任何想法如何解释? 也许这里有人对Windows重叠IO的内部工作有更深入的了解? 或者,您是否可以使用ReadFile读取一个兆字节的数据?
我可以看到一个IO调度程序如何通过最小化search来优化多个请求,特别是当请求是随机访问(它们不是!)时。 我也可以看到一个硬盘如何在NCQ的几个请求中执行类似的优化。
然而,我们正在谈论的荒唐的小数量可笑的小请求,但表面上似乎是明智的2倍。
旁注:明确的赢家是内存映射。 我几乎倾向于加上“毫不奇怪”,因为我是内存映射的狂热粉丝,但在这种情况下,它实际上让我感到惊讶 ,因为“请求”更小,操作系统应该甚至不能预测和安排IO。 起初我没有testing内存映射,因为它可能能够远程竞争,这似乎是违反直觉的。 那么你的直觉,呃。
在不同的偏移处重复映射/取消映射视图实际上几乎为零。 使用一个16MB的视图和每个页面的错误使用一个简单的for()循环读取一个单一字节每页完成在9.2秒@〜111 MB / s。 CPU使用率始终低于3%(一个核心)。 相同的电脑,相同的磁盘,一切都一样。
同样,Windows也一次将8个页面加载到缓冲区caching中,但实际上只创build了一个页面。 每8页错误运行速度相同,并从磁盘加载相同数量的数据,但显示较低的“物理内存”和“系统caching”度量,只有页面错误的八分之一。 后续的读取certificate了这些页面仍然在缓冲区caching中是确定的(没有延迟,没有磁盘活动)。
(可能与内存映射文件非常非常疏远地关联在巨大的顺序读取上更快? )
为了更具说明性:
更新:
使用FILE_FLAG_SEQUENTIAL_SCAN
似乎有点“平衡”128k的读取,提高了100%的性能。 另一方面,它严重影响512k和256k的读取(你不得不想知道为什么?),而对其他任何东西都没有实际影响。 较小块大小的MB / s图表可以说是更“平均”一些,但在运行时没有区别。
我可能已经find了一个更好的小块更好的解释。 如您所知,如果操作系统可以立即提供请求,即从缓冲区(以及各种特定于版本的技术限制),asynchronous请求可以同步运行。
当计算实际的asynchronous读取与“即时”asynchronous读取时,注意到256k以上,Windows会asynchronous运行每个asynchronous请求。 块大小越小,“立即”服务的请求就越多, 即使它们不能立即使用 (即ReadFile只是同步运行)。 我不能弄清楚一个清晰的模式(比如“前100个请求”或者“1000个以上请求”),但是请求大小和同步性之间似乎有一个反向关系。 在8k的块大小下,每个asynchronous请求都被同步服务。
由于某种原因,缓冲同步传输的速度是asynchronous传输速度的两倍(不知道为什么),因此请求大小越小,传输速度越快,因为更多的传输是同步完成的。
对于内存映射预失败,FILE_FLAG_SEQUENTIAL_SCAN会导致性能graphics略有不同(有一个“凹口”向后移动一点),但所花的总时间是完全相同的(同样,这是令人惊讶的,但是我不能帮助它)。
更新2:
无缓冲的IO使得1M,4M和512K请求testing用例的性能曲线图以90s / s的最高速度稍微更高,更“尖锐”,但同样也是最严酷的最小速度,1GB的整体运行时间在+/- 0.5 (具有较小缓冲区大小的请求完成速度明显更快,但是,这是因为超过2558个正在执行的请求,返回了ERROR_WORKING_SET_QUOTA)。 在所有未缓冲的情况下,测量的CPU使用率为零,这并不令人惊讶,因为发生的任何IO都通过DMA运行。
FILE_FLAG_NO_BUFFERING
另一个非常有趣的观察是它显着改变了API的行为。 CancelIO
不再工作,至less不是取消IO 。 凭借无缓冲的CancelIO
请求, CancelIO
将会阻止直到所有请求都完成。 一个律师可能会争辩说,这个职能不能承担责任而忽略了自己的职责,因为在返回时没有更多的空中请求,所以在某种程度上它完成了所要求的 – 但是我对“取消”有些不同。
通过缓冲的重叠IO, CancelIO
将简单地切断绳索,所有CancelIO
操作将立即终止,正如人们所期望的那样。
另一个有趣的事情是,在所有请求完成或失败之前,这个过程是不可驱动的 。 如果操作系统在这个地址空间中进行DMA操作,这种情况是有道理的,但是这是一个令人惊叹的“function”。
我不是文件系统专家,但我认为这里有一些事情要做。 首先, 关于内存映射的评论是赢家。 这并不令人惊讶,因为NT缓存管理器是基于内存映射的 – 通过自己进行内存映射,您可以复制缓存管理器的行为,而不需要额外的内存副本。
从文件中按顺序读取时,缓存管理器会尝试为您预取数据 – 因此您很可能会看到缓存管理器中的预读的效果。 在某些时候,缓存管理器会停止预取读取(或者某些时候预取的数据不足以满足您的读取,因此缓存管理器必须停止)。 这可能是您看到的较大I / O速度下降的原因。
你有没有尝试添加FILE_FLAG_SEQUENTIAL_SCAN到你的CreateFile标志? 这指示预取器更具侵略性。
这可能是违反直觉的,但传统上从磁盘读取数据的最快方法是使用异步I / O和FILE_FLAG_NO_BUFFERING。 当你这样做的时候,I / O直接从磁盘驱动器进入你的I / O缓冲区,没有任何事情可以阻止(假设文件的段是连续的 – 如果不是,文件系统将不得不发出几个磁盘读取以满足应用程序读取请求)。 当然,这也意味着你失去了内置的预取逻辑,不得不推出自己的预取逻辑。 但是通过FILE_FLAG_NO_BUFFERING,你可以完全控制你的I / O管道。
还有一件事要记住:在进行异步I / O时,确保始终有一个I / O请求是非常重要的,否则在最后一次I / O完成到下一个I / O开始。