假设您要尽快处理许多文件,其中处理时间>文件读取时间。
还有,您还有其他一些技巧可以最大化磁盘吞吐量吗?
答案 0 :(得分:1)
我做了一些基准测试以提出一些一般性准则。我测试了约500k个小文件(约14kb)。我认为对于中等大小的文件,结果应该相似;但是对于较大的文件,我怀疑磁盘争用会变得更加重要。如果对操作系统/硬件内部知识更深入的人可以用更具体的解释来补充为什么这个问题,为什么某些事情比其他事情更快,将不胜感激。
我在具有双通道RAM和Linux内核4.18的16虚拟核心(8物理)计算机上进行了测试。
多个线程会增加读取吞吐量吗?
答案是肯定的。我认为这可能是由于1)单线程应用程序的硬件带宽限制或2)当有多个线程发出请求时,操作系统的磁盘请求队列得到了更好的利用。最好的性能是使用virtual_cores*2
线程。吞吐量可能会缓慢下降,这可能是由于磁盘争用增加所致。如果这些页面恰好被缓存在RAM中,那么最好使用大小为virtual_cores
的线程池。但是,如果缓存了<50%的页面(我认为是更常见的情况),那么virtual_cores*2
就可以了。
我认为virtual_cores*2
比virtual_cores
更好的原因是,文件读取还包括一些与磁盘无关的延迟,例如系统调用,解码等。因此,处理器可以交错更有效地线程化:当一个在磁盘上等待时,另一个可以执行与磁盘无关的文件读取操作。 (是否也可能是因为RAM是双通道?)
我测试了读取随机文件还是按顺序读取(通过查找文件在存储中的物理块位置,并以此顺序排序请求)。可以预期,顺序访问将对HDD带来相当大的改善。如果您的应用程序中的限制因素是文件读取时间,而不是处理所述文件,则建议您对顺序访问的请求重新排序,以提高效率。
可以使用异步磁盘IO代替线程池。但是,从我的阅读来看,似乎还没有一种可移植的方法(see this reddit thread)。另外,libuv可以为NodeJS uses a thread pool提供动力来处理其文件IO。
平衡读取吞吐量与处理吞吐量
理想情况下,我们可以在单独的线程中进行读取和处理。在处理第一个文件时,我们可以在另一个线程中排队下一个文件。但是,我们分配用于读取文件的线程越多,处理线程的CPU争用就越多。解决方案是使更快的操作(读取与处理)的线程数最少,同时仍使文件之间的处理延迟为零。这个公式在我的测试中似乎给出了很好的结果:
prop = read_time/process_time
if prop > 1:
# double virtual core count gives fastest reads, as per tests above
read_threads = virtual_cores*2
process_threads = ceil(read_threads/(2*prop))
else:
process_threads = virtual_cores
# double read thread pool so CPU can interleave better, as mentioned above
read_threads = 2*ceil(process_threads*prop)
例如:读取= 2s,过程= 10s;因此每5个处理线程就有2个读取线程
在我的测试中,拥有额外的读取线程只会降低1-1.5%的性能。在我的测试中,对于prop
接近于零的情况,1个读取+ 16个处理线程的吞吐量几乎与32个读取+ 16个处理线程的吞吐量相同。现代线程应该是非常轻量级的,并且如果文件消耗得不够快的话,读线程无论如何都应该处于睡眠状态。 (当prop
很大时,进程线程也应如此)
另一方面,阅读线程太少会产生更大的影响(我的第三个原始问题)。例如,对于非常大的prop
,1个读取+ 16个处理线程比1个读取+ 15个处理线程慢36%。由于进程线程占用了所有基准计算机的内核,因此读取线程具有过多的CPU争用,并且有36%的时间无法使下一个要处理的文件排队。因此,我的建议是改用太多的读取线程。如我上面的公式中所述,将读取线程池的大小加倍即可完成此操作。
旁注:您可以通过将virtual_cores
设置为可用内核的较小百分比来限制应用程序占用的CPU资源。您还可以选择不加倍,因为当有备用内核或更多内核没有执行更密集的处理线程时,CPU争用可能不是问题。
摘要
根据我的测试结果,将线程池与virtual_cores*2
文件读取线程+ virtual_cores
文件处理线程一起使用,可以在各种不同的时序情况下为您提供良好的性能。此配置应为您提供最大吞吐量的约2%,而不必花费大量时间进行基准测试。