Multithreading 如何在处理多个文件时最大限度地提高吞吐量

Multithreading 如何在处理多个文件时最大限度地提高吞吐量,multithreading,optimization,operating-system,filesystems,disk,Multithreading,Optimization,Operating System,Filesystems,Disk,假设您希望尽快处理多个文件,其中处理时间>文件读取时间 使用线程池读取多个文件会增加吞吐量吗?还是只会导致更多的磁盘争用 如果线程池确实有用,那么什么决定了需要多少线程才能达到最大值?这是否可以根据目标系统进行计算 对于单个内核,通过线程异步读取和处理循环会比同步读取和处理循环快吗?我想,既然磁盘延迟如此之高,就应该如此。但是,如果读取的文件比处理时间小得多,那么最好让处理步骤不间断地完成,而不必切换上下文 另外,您还有其他关于最大化磁盘吞吐量的建议吗?我做了一些基准测试,提出了一些一般性的

假设您希望尽快处理多个文件,其中处理时间>文件读取时间

  • 使用线程池读取多个文件会增加吞吐量吗?还是只会导致更多的磁盘争用
  • 如果线程池确实有用,那么什么决定了需要多少线程才能达到最大值?这是否可以根据目标系统进行计算
  • 对于单个内核,通过线程异步读取和处理循环会比同步读取和处理循环快吗?我想,既然磁盘延迟如此之高,就应该如此。但是,如果读取的文件比处理时间小得多,那么最好让处理步骤不间断地完成,而不必切换上下文

另外,您还有其他关于最大化磁盘吞吐量的建议吗?

我做了一些基准测试,提出了一些一般性的指导原则。我用大约500k(14kb)的小文件进行了测试。我认为对于中等大小的文件,结果应该是相似的;但对于较大的文件,我怀疑磁盘争用变得更加严重。如果有人对操作系统/硬件内部结构有更深入的了解,可以用更具体的解释来补充这个答案,解释为什么有些东西比其他东西快,那将不胜感激

我用一台16虚拟核心(8物理)计算机进行了测试,该计算机具有双通道RAM和Linux内核4.18

多线程是否会增加读取吞吐量?

答案是肯定的。我认为这可能是由于1)单线程应用程序的硬件带宽限制,或者2)当许多线程发出请求时,操作系统的磁盘请求队列得到了更好的利用。最好的性能是使用
虚拟内核*2
线程。吞吐量缓慢下降,超过这个范围,可能是因为磁盘争用增加了。如果页面恰好缓存在RAM中,那么最好有一个大小为
virtual\u cores
的线程池。但是,如果<50%的页面被缓存(我认为这是更常见的情况),那么
virtual_cores*2
就可以了

我认为
virtual_cores*2
优于
virtual_cores
的原因在于,文件读取还包括一些与磁盘无关的延迟,如系统调用、解码等。因此,处理器可能可以更有效地交错线程:当一个人在磁盘上等待时,第二种方法是执行与磁盘无关的文件读取操作。(也可能是因为RAM是双通道的吗?)

我测试了读取随机文件与按顺序读取(通过查找文件在存储器中的物理块位置,并按此顺序排列请求)。顺序存取给HDD带来了相当显著的改进,这是意料之中的。如果应用程序中的限制因素是文件读取时间,而不是对所述文件的处理,我建议您重新排序顺序访问请求以获得提升

可以使用异步磁盘IO,而不是线程池。然而,从我的阅读资料来看,似乎还没有一种便携式的方法来实现它()。此外,libuv还支持NodeJS处理其文件IO

平衡读取和处理吞吐量

理想情况下,我们可以在单独的线程中进行读取和处理。在处理第一个文件时,我们可以在另一个线程中将下一个文件排队。但是,我们为读取文件分配的线程越多,与处理线程的CPU争用就越多。解决方案是使更快的操作(读取与处理)的线程数最少,同时仍使文件之间的处理延迟为零。这个公式似乎在我的测试中给出了很好的结果:

prop=读取时间/处理时间
如果道具>1:
#根据上面的测试,双虚拟核心计数提供了最快的读取速度
读线程=虚拟内核*2
进程线程=ceil(读取线程/(2*prop))
其他:
进程线程=虚拟内核
#如上所述,双读线程池使CPU可以更好地进行交叉
读取线程=2*ceil(进程线程*prop)
例如:Read=2s,Process=10s;因此,每5个处理线程有2个读取线程

在我的测试中,有额外的读取线程只会导致大约1-1.5%的性能损失。在我的测试中,对于接近于零的
prop
,1个read+16进程线程的吞吐量几乎与32个read+16进程线程相同。现代线程应该是非常轻量级的,如果文件没有被足够快地消耗,那么读取线程无论如何都应该处于休眠状态。(当
prop
非常大时,进程线程也应该如此)

另一方面,阅读线索太少会产生更大的影响(我的第三个原始问题)。例如,对于非常大的
prop
,1个read+16进程线程比1个read+15进程线程慢36%。由于进程线程占用了所有基准计算机的内核,因此读取线程有太多的CPU争用,并且有36%的时间无法排队等待下一个要处理的文件。因此,我的建议是错误地使用过多的读线程。将上述公式中的读取线程池大小加倍应该可以实现这一点

旁注:通过将
virtual_cores
设置为可用内核的较小百分比,可以限制应用程序消耗的CPU资源。您也可以选择放弃加倍,因为当有一个或多个备用内核没有执行更密集的处理线程时,CPU争用问题可能不会那么严重

摘要

根据我的测试结果,使用带有
virtual_cores*2
file reading threads+
virtual_cores
file processing threads的线程池,可以为各种不同的计时场景提供良好的性能。此配置应使您的吞吐量在最大吞吐量的2%以内,而无需花费大量时间