这个问题是我之前发布的一个问题的后续:Windows fsync (FlushFileBuffers) performance with large files。我在哪里找到了可能的解决方案,但也找到了新问题。
在对 fsynced 写入的不同场景进行基准测试时,我发现了许多令人惊讶的结果。我希望有人可以帮助解释或指出我解释这些结果的信息方向。
该基准测试所做的是将随机块(4096 字节大的页面)以 8 页(32 K)的批次顺序写入文件,然后刷新写入。它总共写入 200000 页,总计 800 MB 和 25000 次刷新。在开始写入之前,文件的大小设置为其最终长度。
它总共支持 4 个选项,其中所有组合都运行:
- 在写入批处理或正常刷新 (NS) 后执行“fsync”/
FlushFileBuffers
操作 (FS)。 - 在开始写入之前将单个字节写入文件的最后一个位置 (LB) 或将文件留空 (E)。
- 使用普通缓冲写入 (B) 或无缓冲/直写 (WT) 写入(使用 FILE_FLAG_NO_BUFFERING 和 FILE_FLAG_WRITE_THROUGH)。
- 直接写入文件流,即通过文件句柄 (F) 或使用内存映射 (MM) 间接写入文件。
下表总结了我在我的系统(带有慢速主轴磁盘的 64 位 Win 7 笔记本电脑)上对这些选项的所有组合的发现。
我发现“fsynced”缓冲写入的性能随着文件的大小呈指数下降到令人难以置信的低吞吐量,这使得在结合大文件时这样做不可行。如果文件的最后一个字节被写入(选项 LB),吞吐量会更低,所以我担心在随机而不是顺序写入场景中,性能会更加显着。
然而,令人惊讶的是,对于无缓冲/直写 I/O,吞吐量保持不变,与文件大小无关。最初(前 100-200 MB)它的吞吐量低于缓冲写入,但之后平均吞吐量迅速赶上,它完成写入 800 MB 的速度大大加快。更令人惊讶的是,如果文件的最后一个字节被写入,吞吐量会增加 2 倍。
当通过内存映射文件写入文件时,性能会出现同样的指数下降,在使用无缓冲/直写标志打开文件的情况下也是如此。同样,如果文件有一个字节写入其最后位置,则性能会更差。
更新 根据霍华德的解释here和here,我在开始写入之前重新运行测试而不创建新文件(即打开现有的、完全写入的文件并覆盖它)。我已经更新了原始问题中的代码,以反映为此测试所做的更改。结果部分符合他对 Linux 的解释和发现。但也有一些值得注意的例外。下表提供了结果,红色突出显示显着变化,蓝色突出显示未发生变化的情况,这令人惊讶(即,如果霍华德解释中提到的影响是唯一起作用的影响,则与预期不符)。
对于使用“fsync”刷新的缓冲写入文件(即不通过 memmap),性能现在已从指数衰减变为恒定趋势。但是,现在比以前的测试场景需要更长的时间。吞吐量是恒定的 1.5 MB/s,之前它从 20 MB/s 左右开始呈指数衰减到 1.5 MB/s 左右。似乎一种可能的解释是文件元数据在每次刷新时也会被刷新,从而导致整个磁盘旋转以寻找元数据的位置。
对于“直写”到文件的场景,是否写入最后一个字节的结果现在是相同的,这与霍华德解释的预期一致。
然而,除了一个值得注意的例外,对内存映射的写入并没有真正改变,这令人惊讶。它们仍然显示出相同的写入性能指数衰减(从大约 20 MB/s 开始衰减到 1.8 MB/s)。这表明存在不同的机制。一个值得注意的例外是,如果底层文件是在没有 FILE_FLAG_WRITE_THROUGH 的情况下创建的,并且执行了“fsync”刷新。现在,此场景显示了恒定(较差)的性能,吞吐量约为 1.6 MB/s。由于我有一些疑问,我多次重新运行这个场景,每次都给出相同的结果。
为了进一步了解,我还使用一个较小的文件(50000 页,总计 200 MB)重新运行了这个测试,以确认 fsync 性能(对于缓冲 I/O)实际上确实取决于文件大小。结果如下所示,值得特别注意的结果以红色突出显示。
这些结果与较大文件的结果非常相关。值得注意的变化是,对于那些突出显示的内容,写入的性能更高一些,它们似乎达到了大约 7 MB/s 的限制。
根据迄今为止对我的系统的观察,总结为高度推测性的结论:
- 带有缓冲 IO 的文件(即没有 FILE_FLAG_WRITE_THROUGH 标志)在 Windows 上的“fsync”性能随着已写入文件的字节数呈指数下降。原因似乎是每次都需要刷新文件元数据,这会导致磁盘查找到文件的开头。
- 写入内存映射文件时,Windows 上的“fsync”性能也显示出指数级下降的性能。我目前没有解释导致这种情况的确切机制。
鉴于观察到的性能,至少对于我的用例而言,这两个 I/O 选项并不代表可行的解决方案。
根据Greg 的建议,我将在关闭 Windows 磁盘缓存的情况下重新运行测试,并且我还将运行 Howard 提供的基准代码,以排除由于我自己的错误导致结果出现偏差的可能性。
更新 2 我已经完成了测试,目前正在编译结果。为了不写“完整的历史”,我将用结果、发现和一些结论的总结来替换这个问题的当前内容。Howard 对这个问题的回答,以及在 .NET 代码旁边运行他的 c 基准代码的能力是最有用的。这些结果与应用程序的相关性非常好。Rlb 的回答帮助我更好地了解与磁盘相关的“合理数字”是什么。谢谢。
部分问题仍未得到解答。特别与写入内存映射时观察到的性能下降(和文件大小相关)有关。它可能与搜索/元数据刷新有关,但我还不清楚为什么/如何。