如何在处理多个文件时最大化吞吐量

Posted

技术标签:

【中文标题】如何在处理多个文件时最大化吞吐量【英文标题】:How to maximize throughput when processing many files 【发布时间】:2019-07-28 20:30:25 【问题描述】:

假设您想尽快处理许多文件,其中处理时间 > 文件读取时间。

使用线程池读取多个文件会增加吞吐量吗?还是只会导致更多的磁盘争用? 如果线程池确实有帮助,是什么决定了需要多少线程才能达到最大值?这个可以根据目标系统计算吗? 对于单核,通过线程异步读取和处理循环会比同步执行更快吗?我假设由于磁盘延迟如此之高,它会是。但也许如果读取的文件远小于处理时间,最好让处理步骤在没有上下文切换的情况下不间断地完成。

另外,您还有其他最大化磁盘吞吐量的技巧吗?

【问题讨论】:

任何其他最大化磁盘吞吐量的技巧?购买更快的磁盘并永远解决问题,而不必担心处理算法中的错误,花费时间和金钱编写代码,并且必须在将来维护所有这些代码。 这取决于很多因素。操作系统、可用 CPU、可用内存、磁盘性能等。当文件很小 @AndrewHenle 记住这一点总是好的。不过,如果该软件打算在各种不同的硬件/操作系统配置(如框架)上运行,您仍然需要采用一些基于软件的技术。 @user743414 尽管有许多可能的配置,但我怀疑从磁盘读取数据的内部结构对于各种主板、CPU、RAM 等都是非常相似的。我希望有更多专业知识的人内部结构可以描述一般原则,而无需跨多个钻机进行基准测试。 【参考方案1】:

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

我使用具有双通道 RAM 和 Linux 内核 4.18 的 16 个虚拟内核(8 个物理)计算机进行了测试。

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

答案是肯定的。我认为这可能是由于 1)单线程应用程序的硬件带宽限制或 2)当许多线程发出请求时,操作系统的磁盘请求队列得到了更好的利用。最好的性能是使用virtual_cores*2 线程。除此之外,吞吐量会缓慢下降,这可能是因为磁盘争用增加。如果页面碰巧缓存在 RAM 中,那么最好有一个大小为virtual_cores 的线程池。但是,如果 virtual_cores*2 就可以了。

我认为virtual_cores*2virtual_cores 更好的原因是文件读取还包括一些与磁盘无关的延迟,如系统调用、解码等。所以也许处理器可以更有效地交错线程:当一个在磁盘上等待时,第二个可以执行与磁盘无关的文件读取操作。 (会不会也是因为内存是双通道的?

我测试了读取随机文件与顺序读取(通过查找文件在存储中的物理块位置,并以此排序请求)。顺序访问对 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%,而无需花费大量时间进行基准测试。

【讨论】:

另一件要提的事情是,如今,SSD 通常会在队列深度方面宣传其随机读取性能,例如QD16 是 16 个线程请求 4kb 数据时的读取性能。您可以使用它来判断哪种队列深度(异步磁盘读取线程)是最佳的。 这是关于异步文件 IO 的另一个相关问题,而不是使用线程池:***.com/questions/13407542/…

以上是关于如何在处理多个文件时最大化吞吐量的主要内容,如果未能解决你的问题,请参考以下文章

采用spring batch 处理大数据量,瓶颈在数据库吞吐量时,该如何优化?

什么是QPS、TPS、RT、吞吐量?

使用 .Net 核心应用程序发送到 EventHubs 时如何优化吞吐量

iperf(ubuntu)

Apache Ignite 吞吐量、值大小和最大缓存计数?

交换机转发速率吞吐量背板带宽计算(详解)