我想问一个问题,然后用我自己的答案跟进,但也看看其他人有什么答案。
我们有两个大文件,我们想要同时从两个独立的线程中读取。一个线程将顺序读取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时都是可重复的。
答案 0 :(得分:11)
问题似乎出现在Windows I / O调度策略中。根据我发现的here,有很多方法可以获得O.S.安排磁盘请求。虽然Linux和其他人可以在不同的策略之间进行选择,但在Vista Windows被锁定在单个策略之前:FIFO队列,其中所有请求以64 KB块分割。我相信这个策略是您遇到问题的原因:调度程序将混合来自两个线程的请求,导致磁盘不同区域之间的连续搜索。
现在,好消息是,根据here和here,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在从一个线程移动到两个线程时会受到影响。以下是测试机器的摘要:
所有这些系统的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位机器上映射多个整个文件,这意味着这并不像它可能那么简单。