假设您希望尽快处理许多文件,其中处理时间>文件读取时间。
另外,您是否有其他最大化磁盘吞吐量的技巧?
我做了一些基准测试来提出一些通用指南。我测试了大约~500k的小(~14kb)文件。我认为中等大小的文件结果应该类似;但对于较大的文件,我怀疑磁盘争用变得更加重要。如果对操作系统/硬件内部有更深入了解的人可以用更具体的解释来解释为什么某些事情比其他事情更快,那将是值得赞赏的。
我用16通道核心(8物理)计算机测试了双通道RAM和Linux内核4.18。
多线程是否会增加读取吞吐量?
答案是肯定的。我认为这可能是由于1)单线程应用程序的硬件带宽限制或2)当许多线程发出请求时,OS的磁盘请求队列被更好地利用。最好的表现是使用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)
例如:Read = 2s,Process = 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%范围内,而无需花费大量时间进行基准测试。