在开发将整个大文件(> 400MB)预先保存到缓冲区高速缓存中以加速实际运行的应用程序之前,我测试了一次读取4MB是否仍然具有比一次读取1MB大小的显着优点。令人惊讶的是,较小的请求实际上变得更快。这似乎是反直觉的,所以我进行了更广泛的测试。
缓冲区缓存在运行测试之前被清除(只是为了笑,我也在缓冲区中的文件中运行缓冲区高速缓存缓存缓存缓存缓存缓存高达2GB / s,无论请求大小如何,尽管有惊人的/ – 30%随机方差)。
所有读取使用的重复ReadFile与相同的目标缓冲区(该句柄使用FILE_FLAG_OVERLAPPED打开并且没有FILE_FLAG_NO_BUFFERING)。使用的硬盘有点老,但功能完整,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返回之前的时间)随着请求数量的增加而大幅度增加,但异步完成时间几乎减半。
内核cpu时间在每种情况下都是大约5-6%(在四核上,所以应该真的说20-30%),而异步读取正在完成,这是一个惊人的数量的cpu – 显然操作系统做了一些非 – 也是负责人的忙碌等待。在2.6 GHz下,30%的cpu为25秒,这是做“无”的几个周期。
有什么想法可以解释吗也许这里有人对Windows重叠IO的内部工作有更深入的了解?或者,您可以使用ReadFile读取一兆字节数据的想法有什么实质性的错误?
我可以看到IO调度程序如何通过最小化搜索来优化多个请求,特别是当请求是随机访问(它们不是!)时)。我还可以看到硬盘在NCQ中有几个请求能够执行类似的优化。
然而,我们正在谈论可笑的小数量的可笑数量,但仍然优于两个因素之间似乎是合理的。
Sidenote:明显的赢家是记忆映射。我几乎倾向于增加“不奇怪”,因为我是记忆映射的大粉丝,但在这种情况下,它实际上让我感到惊讶,因为“请求”更小,操作系统应该能够预测更少,安排IO。我没有开始测试内存映射,因为它似乎是反直觉的,它可能能够远程竞争。呃这么多的直觉呢
以不同的偏移重复映射/解映射实际上几乎为零。使用一个16MB的视图和错误的每个页面与一个简单的for()循环读取单个字节每页完成在9.2秒@〜111 MB / s。 cpu使用率始终在3%以下(一核心)。相同的电脑,同样的磁盘,一切都一样。
同样看来,Windows一次只能将8页加载到缓冲区缓存中,但实际上只创建了一个页面。每第8页的故障以相同的速度运行,并从磁盘加载相同数量的数据,但显示较低的“物理内存”和“系统缓存”度量,并且只有页面错误的1/8。随后的读取证明页面在缓冲区高速缓存中是明确的(没有延迟,没有磁盘活动)。
(可能非常非常与Memory-Mapped File is Faster on Huge Sequential Read?相关)
为了更多的说明:
更新:
使用FILE_FLAG_SEQUENTIAL_SCAN似乎有些“平衡”读取128k,提高了100%的性能。另一方面,它严重影响512k和256k的读取(你必须想知道为什么),并且对其他任何东西都没有真正的影响。较小的块大小的MB / s图可以说似乎更“偶”,但运行时没有差别。
我可能已经找到一个更小的块大小表现更好的解释。如你所知,异步请求可以同步运行,如果操作系统可以立即提供请求,即从缓冲区(以及针对各种特定于版本的技术限制)。
当考虑实际异步和“立即”异步读取时,注意到高于256k,Windows异步运行每个异步请求。即使它们不能立即使用(即ReadFile只是同步运行),更大的请求被“立即”提供。我不能做出明确的模式(如“前100个请求”或“超过1000个请求”),但是请求大小和同步性之间似乎存在反相关。在8k的块大小时,每个异步请求都是同步的。
由于某些原因,缓冲同步传输是异步传输的两倍(不知道为什么),因此请求大小越小,总体传输越快,因为更多的传输是同步完成的。
对于内存映射预失效,FILE_FLAG_SEQUENTIAL_SCAN会导致性能图形稍微不同的形状(有一个“缺口”被向后移动),但总时间完全相同(再次,这是令人惊讶的,但我不能帮助它)。
更新2:
无缓冲的IO使得1M,4M和512k请求测试用例的性能曲线在GB / s的90年代更高,更“spiky”的最大值,但是在苛刻的最小值的情况下,1GB的整体运行时间在/ – 0.5s的缓冲运行(具有较小缓冲区大小的请求完成速度明显更快,但是这是因为超过2558个空中请求,返回了ERROR_WORKING_SET_QUOTA)。在所有无缓冲的情况下,测量的cpu使用率为零,这是不奇怪的,因为发生的任何IO都会通过DMA运行。
FILE_FLAG_NO_BUFFERING的另一个非常有趣的观察是它显着改变了API行为。 CancelIO不再起作用,至少没有取消IO的意义。在无缓冲的机上请求中,CancelIO将会被阻止,直到所有请求完成。一名律师可能会认为,这个职能不能因为忽视其职责而承担责任,因为在返回时没有更多的飞行中请求,所以在某种程度上它已经做了所要求的 – 但是我对“取消”的理解有点不一样
通过缓冲,重叠的IO,CancelIO将简单地切割绳索,所有的飞行操作都会立即终止,如预期的那样。
另一个有趣的是,在所有请求完成或失败之前,该进程是不可用的。如果操作系统正在将DMA写入该地址空间,这种做法是有道理的,但这是一个令人惊叹的“功能”。
当您从文件中顺序读取时,缓存管理器会尝试为您预取数据,因此您可能会在缓存管理器中看到readahead的影响。在某些时候,缓存管理器停止预取读取(或者在某些时候,预取的数据不足以满足您的读取,因此缓存管理器必须停止)。这可能解释了您看到的较大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之间失去潜在的时间开始了