如何从磁盘获得良好的并发读取性能

时间:2008-08-12 19:50:13

标签: windows multithreading file-io

我想问一个问题,然后用我自己的答案跟进,但也看看其他人有什么答案。

我们有两个大文件,我们想要同时从两个独立的线程中读取。一个线程将顺序读取fileA,而另一个线程将顺序读取fileB。线程之间没有锁定或通信,两者都尽可能快地顺序读取,并且两者都立即丢弃它们读取的数据。

我们在Windows上使用此设置的经验非常糟糕。两个线程的组合吞吐量大约为2-3 MiB / sec。驱动器似乎花费大部分时间在两个文件之间寻找后退和前进,大概在每次搜索后读取很少。

如果我们禁用其中一个线程并暂时查看单个线程的性能,那么我们可以获得更好的带宽(此机器约为45 MiB /秒)。很明显,糟糕的双线程性能是操作系统磁盘调度程序的假象。

我们可以采取哪些措施来提高并发线程读取性能?也许可以通过使用不同的API或以某种方式调整操作系统磁盘调度程序参数。

一些细节:

在具有2GiB RAM的计算机上,文件大小为2 GiB。出于这个问题的目的,我们认为它们不会被缓存并完美地进行碎片整理。我们使用了碎片整理工具并重新启动以确保是这种情况。

我们没有使用特殊的API来读取这些文件。这种行为可以在各种沼泽标准API中重复,例如Win32的CreateFile,C的fopen,C ++的std :: ifstream,Java的FileInputStream等。

每个线程在一个循环中旋转,调用read函数。我们改变了每次迭代从API请求的字节数,从1KiB到128MiB之间的值。改变这一点没有任何影响,因此在每次磁盘搜索之后,OS实际读取的数量不是由这个数字决定的。这正是应该期待的。

单线程和双线程性能之间的巨大差异在Windows 2000,Windows XP(32位和64位),Windows Server 2003以及使用和不使用硬件RAID5时都是可重复的。

6 个答案:

答案 0 :(得分:11)

问题似乎出现在Windows I / O调度策略中。根据我发现的here,有很多方法可以获得O.S.安排磁盘请求。虽然Linux和其他人可以在不同的策略之间进行选择,但在Vista Windows被锁定在单个策略之前:FIFO队列,其中所有请求以64 KB块分割。我相信这个策略是您遇到问题的原因:调度程序将混合来自两个线程的请求,导致磁盘不同区域之间的连续搜索。
现在,好消息是,根据herehere,Vista引入了一个更智能的磁盘调度程序,您可以在其中设置请求的优先级,并为您的流程分配最小的带宽。
坏消息是我发现在以前版本的Windows中无法更改磁盘策略或缓冲区大小。此外,即使提高进程的磁盘I / O优先级将提高其他进程的性能,您仍然会遇到线程相互竞争的问题。
我可以建议通过引入自制的磁盘访问策略来修改您的软件 例如,您可以在线程B中使用这样的策略(类似于线程A):

if THREAD A is reading from disk then wait for THREAD A to stop reading or wait for X ms
Read for X ms (or Y MB)
Stop reading and check status of thread A again  

您可以使用信号量进行状态检查,也可以使用perfmon计数器来获取实际磁盘队列的状态。 X和/或Y的值也可以通过检查实际的转移率并自动调整它们来自动调整,从而在应用程序在不同的机器和/或O.S上运行时最大化吞吐量。您可以发现缓存,内存或RAID级别以某种方式影响它们,但通过自动调整,您将始终在每种方案中获得最佳性能。

答案 1 :(得分:5)

我想在回复中添加一些进一步的说明。我们测试的所有其他非Microsoft操作系统都没有遇到此问题。 Linux,FreeBSD和Mac OS X(不同硬件上的最后一个)在从一个线程移动到两个线程时,在聚合带宽方面都优雅地降级。例如,Linux从~45 MiB / sec降低到~42 MiB / sec。这些其他操作系统必须在每次搜索之间读取更大的文件块,因此不会花费大量所有时间等待磁盘进行搜索。

我们的Windows解决方案是将FILE_FLAG_NO_BUFFERING标记传递给CreateFile,并在每次调用ReadFile时使用大(~16MiB)读取。由于以下几个原因,这是次优的:

  • 当像这样读取时,文件不会被缓存,因此缓存通常没有任何优点。
  • 使用此标志时的约束比正常读​​取(读取缓冲区与页面边界的对齐等)复杂得多。

(作为最后的评论。这是否解释了为什么在Windows下进行交换是如此地狱般的?即,Windows无法以任何效率同时对多个文件执行IO,因此在交换所有其他IO操作时,不得不特别慢。 )


编辑以添加Will Dean的更多详细信息:

当然,在这些不同的硬件配置中,原始数据确实发生了变化(有时是实质性的)。然而问题是性能的持续降低,只有Windows在从一个线程移动到两个线程时会受到影响。以下是测试机器的摘要:

  • 使用单驱动器运行Windows 2000,Windows XP(32位)和Windows XP(64位)的多个戴尔工作站(英特尔至强)。
  • 运行Windows Server 2003(64位)且RAID 1 + 0的Dell 1U服务器(Intel Xeon)。
  • 配备Windows XP(64位),Windows Server 2003和硬件RAID 5的HP工作站(AMD Opteron)。
  • 我的家用无品牌PC(AMD Athlon64)运行Windows XP(32位),FreeBSD(64位)和Linux(64位),单驱动器。
  • 我的家用MacBook(Intel Core1)运行Mac OS X,单个SATA驱动器。
  • 我的家Koolu运行Linux的PC。与其他系统相比,它的功能非常不足,但我证明,在进行多线程磁盘读取时,即使是这台机器也可以胜过带有RAID5的Windows服务器。

所有这些系统的CPU使用率在测试过程中非常低,并且禁用了防病毒软件。

我之前忘了提及但我们也尝试了设置CreateFile标志的普通Win32 FILE_FLAG_SEQUENTIAL_SCAN API。这个标志没有解决问题。

答案 2 :(得分:1)

看起来有点奇怪的是,你看到各种各样的Windows版本没有区别,单个驱动器和硬件raid-5之间没有任何区别。

这只是'直觉',但这确实让我怀疑这是一个简单的寻求问题。除了OS X和Raid5之外,所有这些都是在同一台机器上试过的 - 你试过另一台机器吗?在此测试期间,您的CPU使用率基本为零吗?

您可以编写哪个最短的应用程序来演示此问题? - 我有兴趣在这里试试。

答案 3 :(得分:0)

我会在内存中创建某种线程安全锁。每个线程都可以等待锁定,直到它被释放。当锁变为空闲时,取出锁并读取文件一段规定的时间或一定数量的数据,然后释放其他任何等待线程的锁。

答案 4 :(得分:0)

您在Windows下使用IOCompletionPorts吗?通过C ++的Windows有一个关于这个主题的深入章节,幸运的是,it is also available on MSDN

答案 5 :(得分:0)

保罗 - 看到了更新。非常有趣。

在Vista或Win2008上尝试它会很有趣,因为人们似乎在某些情况下报告了一些相当大的I / O改进。

我对不同API的唯一建议是尝试对文件进行内存映射 - 你试过吗?不幸的是,每个文件2GB,你将无法在32位机器上映射多个整个文件,这意味着这并不像它可能那么简单。