我想问一个问题,然后用我自己的答案跟进,但也看看其他人有什么答案.
我们有两个大文件,我们想同时从两个独立的线程中读取.一个线程将顺序读取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时都是可重复的.
问题似乎是在Windows I/O调度策略中.根据我在此处发现的内容,操作系统有许多方法可以调度磁盘请求.虽然Linux和其他人可以在不同的策略之间进行选择,但在Vista Windows被锁定在单个策略之前:FIFO队列,其中所有请求以64 KB块的形式分割.我相信这个策略是您遇到问题的原因:调度程序将混合来自两个线程的请求,导致磁盘的不同区域之间的连续搜索.
现在,好消息是,根据这里和这里,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的值也可以通过检查实际的传输速率自动调整并慢慢修改它们,从而在应用程序在不同的机器和/或操作系统上运行时最大化吞吐量您可以找到缓存,内存或RAID级别以某种方式影响它们,但通过自动调整,您将始终在每种情况下获得最佳性能.
我想在回复中添加一些补充说明.我们测试的所有其他非Microsoft操作系统都没有遇到此问题.Linux,FreeBSD和Mac OS X(不同硬件上的最后一个)在从一个线程移动到两个线程时,在聚合带宽方面都优雅地降级.例如,Linux从~45 MiB/sec降低到~42 MiB/sec.这些其他操作系统必须在每次搜索之间读取更大的文件块,因此不会花费几乎所有时间等待磁盘寻找.
我们的Windows解决方案是将FILE_FLAG_NO_BUFFERING
标志传递给CreateFile
每次调用时使用大(~16MiB)读取ReadFile
.由于以下几个原因,这是次优的:
这样读取时文件不会被缓存,因此缓存通常没有任何优点.
使用此标志时的约束比正常读取(读取缓冲区与页面边界的对齐等)复杂得多.
(作为最后的评论.这是否解释了为什么在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).
HP工作站(AMD Opteron),带有Windows XP(64位),Windows Server 2003和硬件RAID 5.
我的家用无品牌PC(AMD Athlon64)运行Windows XP(32位),FreeBSD(64位)和Linux(64位),单驱动器.
我的家用MacBook(Intel Core1)运行Mac OS X,单SATA驱动器.
我的家Koolu PC运行Linux.与其他系统相比,它的功能非常不足,但我证明了在进行多线程磁盘读取时,即使是这台机器也可以胜过带有RAID5的Windows服务器.
在测试期间,所有这些系统的CPU使用率都非常低,并且禁用了防病毒软件.
我之前忘了提到,但我们也尝试了标准设置的普通Win32 CreateFile
API FILE_FLAG_SEQUENTIAL_SCAN
.这个标志没有解决问题.