繁体   English   中英

需要多少线程才能使它们成为糟糕的选择?

[英]How many threads does it take to make them a bad choice?

我必须使用boost :: thread在C ++中编写一个不那么大的程序。

手头的问题是处理大量(可能是数千或数万。数百和毫秒也是可能的)多个(可能)大文件。 每个文件都独立于另一个文件,它们都位于同一目录中。 我想使用多线程aproach,但问题是,我应该使用多少线程? 我的意思是,数量级是多少? 10,500,12400?

存在一些同步问题,每个线程应返回一个值结构(为每个文件累积),并将这些结构添加到“全局”结构中以获取整体数据。 我意识到一些线程可能因为同步而“饿了”,但如果它只是一个添加操作,那有关系吗?

我在想

for(each file f in directory){

    if (N < max_threads)//N is a static variable controlling amount of threads
         thread_process(f)
    else
       sleep()
}

这是在HP-UX中,但我无法经常测试它,因为它是一个远程且非常难以访问的服务器。

根据Herb Sutter在他的文章中讨论的Amdahl定律:

一些程序的处理完全是“O(N)”并行化(称为p部分),只有那部分可以直接在具有越来越多处理器核心的机器上扩展。 该程序的其余工作是“O(1)”顺序。 [1,2]假设完全使用所有可用内核而没有并行化开销,Amdahl定律表示在具有N个内核的机器上实现该程序工作负载的最佳加速由下式给出:
公式图像

在您的情况下,I / O操作可能需要大部分时间,以及同步问题。 您可以计算阻塞(?)慢速I / O操作所花费的时间,并大致找到适合您任务的线程数。


可以在此处找到Herb Sutter的并发相关文章的完整列表。

我对HP / UX不太了解,但在Windows世界中,我们使用线程池来解决这类问题。 Raymond Chen 一段时间后写了这个 ,事实上......

如果线程数超过系统中CPU核心数的2倍,我通常不会期望在CPU绑定负载上能够很好地扩展任何东西。 对于I / O绑定负载,您可能能够获得更多,具体取决于您的磁盘子系统的速度,但是一旦达到大约100左右,我会认真考虑更改模型......

详细说明它真的取决于

IO boundedness of the problem
    how big are the files
    how contiguous are the files
    in what order must they be processed
    can you determine the disk placement
how much concurrency you can get in the "global structure insert"
    can you "silo" the data structure with a consolidation wrapper
the actual CPU cost of the "global structure insert" 

例如,如果您的文件驻留在3 TB的闪存阵列上,那么解决方案与它们驻留在单个磁盘上的情况不同(如果“全局结构插入”占用的数量少于读取的问题,那么I / O就会受到影响同样有一个带2个螺纹的2级管道 - 读取阶段为插入阶段供电。)

但在这两种情况下,架构可能都是2级的垂直管道。 n读取线程和m写入线程,其中n和m由该阶段的“自然并发”确定。

为每个文件创建一个线程可能会导致磁盘抖动。 就像你将CPU绑定进程的线程数量定制为自然可实现的CPU并发(并且高于创建上下文切换开销AKA颠簸)在I / O端也是如此 - 从某种意义上说,你可以想到磁盘颠簸为“磁盘上的上下文切换”。

你说文件都在一个目录中。 这是否意味着他们都在一个物理驱动器上?

如果是这样,并假设它们尚未缓存,那么您的工作将是保持单个读头忙,并且没有多少线程可以帮助它。 实际上,如果由于并行性而必须在轨道之间跳转,则可以减慢速度。

另一方面,如果计算部分花费大量时间,导致读头必须等待,那么有> 1个线程可能是有意义的。

通常,使用线程来提高性能是没有意义的,除非它允许您同时使并行的硬件工作。

更常见的是,线程的价值在于,例如,跟踪多个同时进行的对话,例如,如果您有多个用户,每个线程可以等待自己的Johny或Suzy而不会感到困惑。

如果工作负载在I / O边界附近听起来很接近,那么您可能会获得最大吞吐量,其中包含与您拥有的主轴数相同的线程数。 如果您有多个磁盘且所有数据都在同一个RAID 0上,则可能不需要任何多个线程。 如果多个线程试图访问磁盘的非顺序部分,操作系统必须停止读取一个文件,即使它可能正好在头部下方,并移动到磁盘的另一部分以服务另一个线程,因此它没有饿死。 只有一个线程,磁盘需要永远不会停止读取以移动磁头。

显然,这取决于访问模式是非常线性的(例如视频重新编码)和数据实际上是在磁盘上未分段,这取决于很多。 如果工作负载更多的是CPU限制,那么它就没那么重要了,你可以使用更多的线程,因为无论如何磁盘都会翻转它的拇指。

正如其他海报所示,首先介绍!

不要听起来陈词滥调,但你可以根据需要使用尽可能多的线程。

基本上,您可以根据(实际)完成时间绘制线程数的图表。 您还可以绘制一个总线程到总线程时间的线程。

第一个图表将帮助您确定CPU功率瓶颈所在的位置。 在某些时候,您将成为I / O绑定(意味着磁盘无法足够快地加载数据)或线程数将变得如此之大,这将影响机器的性能。

第二个确实发生了。 我看到一段代码最终创建了30,000多个线程。 通过将其限制为1,000,它最终变得更快。

另一种看待这个问题的方法是:速度有多快? I / O成为瓶颈的地方是一回事,但你可能会在那之前达到“足够快”的地步。

使用线程池而不是为每个文件创建一个线程。 编写解决方案后,您可以轻松调整线程数。 如果作业彼此不相关,我会说线程数应该等于核心数/ cpus。

答案在某种程度上取决于您需要对每个文件执行的CPU处理密集程度。

在处理时间占据I / O时间的一个极端情况下,线程带来的好处就是能够利用多个内核(可能还有超线程)来利用CPU的最大可用处理能力。 在这种情况下,您希望针对大量等于系统上逻辑核心数的工作线程。

在I / O是你的瓶颈的另一个极端你不会从多线程中看到太多好处,因为他们将花费大部分时间在等待I / O完成。 在这种情况下,您需要专注于最大化I / O吞吐量而不是CPU利用率。 在单个未分段的硬盘驱动器或DVD上,您有多个线程的I / O绑定可能会损害性能,因为您可以从单个线程上的顺序读取获得最大的I / O吞吐量。 如果驱动器碎片化或者您具有RAID阵列或类似驱动器,则同时在飞行中同时具有多个I / O请求可能会提高您的I / O吞吐量,因为控制器可能能够智能地重新排列它们以实现更高效的读取。

我认为将此视为两个独立的问题可能会有所帮助。 一个是如何获得文件读取的最大I / O吞吐量,另一个是如何最大限度地利用CPU来处理文件。 通过让少量I / O线程启动I / O请求并且工作线程池大致等于处理数据的逻辑CPU核心数量,可能会获得最佳吞吐量。 是否值得努力实现更复杂的设置,这取决于您的特定问题的瓶颈在哪里。

这听起来可能有点太老了,但你考虑过简单的分叉过程吗? 听起来你有高度独立的工作单位,返回数据的聚合很少。 流程模型还可以释放虚拟地址空间(如果您使用的是32位计算机,可能会很紧张),允许每个工作室对正在处理的整个文件说mmap()。

有许多变量会影响性能(操作系统,文件系统,硬盘速度与CPU速度,数据访问模式,读取后对数据进行的处理等)。

因此,最好的办法是在代表性的数据集上尝试每个可能的线程计数的测试运行(如果可能的话,一个大的数据集,以便文件系统缓存不会使结果偏差太大),并记录每个线程需要多长时间时间。 从单个线程开始,然后使用两个线程再次尝试,依此类推,直到您觉得有足够的数据点为止。 最后,您应该将数据绘制成一条漂亮的曲线,以指示“最佳位置”的位置。 您应该能够循环执行此操作,以便在一夜之间自动编译结果。

我同意每个人建议一个线程池:你用池安排任务,并且池分配线程来完成任务。

如果您受CPU限制,只要CPU使用率低于100%,就可以继续添加线程。 当您受I / O限制时,磁盘颠簸可能会在某些时候阻止更多线程提高速度。 你必须自己找出来。

你见过英特尔的线程构建模块吗? 请注意,我无法评论这是否是您所需要的。 我在Windows上制作了一个小玩具项目,那是几年前的事。 (它有点类似于你的,BTW:它递归地遍历文件夹层次结构并计算它找到的源代码文件中的行。)

更多线程不一定会为您提供更高的吞吐量。 线程具有非常重要的成本,无论是创建(在CPU时间和OS资源方面)还是运行(在内存和调度方面)。 而且你拥有的线程越多,与其他线程争用的可能性就越大。 添加线程有时甚至会降低执行速度。 每个问题都略有不同,你最好写一个漂亮,灵活的解决方案,并试验参数,看看什么效果最好。

您的示例代码,为每个文件生成一个线程,几乎会立即淹没系统,使max_threads值超过10左右。正如其他人所建议的那样,带有工作队列的线程池就是您可能想要的。 每个文件都是独立的这一事实很好,因为这使得它几乎令人尴尬地并行(除了每个工作单元末尾的聚合)。

一些会影响您的吞吐量的因素:

  • CPU核心数
  • 磁盘通道数(主轴,RAID设备等)
  • 处理算法,以及问题是CPU还是I / O绑定
  • 争用主统计结构

去年我写了一个与你描述的基本相同的应用程序。 我最终使用了Python和pprocess 它使用了一个带有工作进程池的多进程模型,通过管道(而不是线程)进行通信。 主进程将读取工作队列,将输入切换为块,并将块信息发送给工作人员。 工作人员会处理数据,收集统计信息,并在完成后将结果发送回主人。 主人将结果与全局总数相结合,并向工人发送另一个块。 我发现它几乎线性地扩展到8个工作线程(在一个8核盒子上,这是相当不错的),除此之外它降级了。

有些事情需要考虑:

  • 使用具有工作队列的线程池,其中线程数可能与系统中的核心数相关
  • 或者,使用多进程设置,通过管道进行通信
  • 使用mmap() (或等效的)来评估输入文件的内存映射,但只有在您分析了基线情况之后
  • 以块大小的倍数读取数据(例如,4kB),并将其切换为存储器中的行
  • 从一开始就构建详细的日志记录,以帮助调试
  • 更新主统计数据时请密切注意争用,尽管数据的处理和读取时间可能会淹没
  • 不要做出假设 - 测试和衡量
  • 设置一个尽可能接近部署系统的本地开发环境
  • 使用数据库(如SQLite )来处理状态数据,处理结果等
  • 数据库可以跟踪哪些文件已被处理,哪些行有错误,警告等
  • 只允许您的应用程序以只读方式访问原始目录和文件,并将结果写入其他位置
  • 小心不要尝试处理由另一个进程打开的文件(这里有一些技巧)
  • 小心你没有达到每个目录的文件数量的操作系统限制
  • 描述一切,但一定要一次只改变一件事,并保留详细的记录。 性能优化很难
  • 设置脚本,以便您可以始终重新运行测试。 拥有数据库会对此有所帮助,因为您可以删除将文件标记为已处理并针对相同数据重新运行的记录。

当您描述的一个目录中有大量文件时,除了可能达到文件系统限制之外,还需要时间统计目录并找出您已经处理过哪些文件以及哪些文件仍需要显着增加。 例如,考虑按日期将文件分解为子目录。

关于性能分析的另一个词:在将小型测试数据集的性能外推到超大型数据集时要小心。 你不能。 我发现了一个很难的方法,你可以达到某一点,我们在编程中每天对资源的定期假设不再适用。 例如,我发现当我的应用程序超越它时,MySQL中的语句缓冲区是16MB! 保持8个内核忙可以占用大量内存,但如果你不小心,你可以轻松地咀嚼2GB内存! 在某些时候,您必须测试生产系统上的实际数据,但是给自己一个安全的测试沙箱来运行,因此您不需要生成生产数据或文件。

与此讨论直接相关的是Tim Bray博客上的一系列文章,名为“Wide Finder”项目 问题只是解析日志文件并生成一些简单的统计信息,但在多核系统上以最快的方式。 许多人以各种语言提供解决方案。 绝对值得一读。

最简单的线程有多昂贵取决于操作系统(您可能还需要调整一些操作系统参数以超过一定数量的线程)。 至少每个都有自己的CPU状态(寄存器/标志,包括浮点)和堆栈以及任何特定于线程的堆存储。

如果每个单独的线程不需要太多不同的状态,那么你可以通过使用小的堆栈大小来使它们相当便宜。

在极限情况下,您可能最终需要使用非OS协作线程机制,甚至使用微小的“执行上下文”对象自己复用事件。

刚开始使用线程并稍后担心:)

作为一个球场号码,您应该将线程数保持在10到100之间,以最大限度地减少锁争用和上下文切换开销。

这里有两个问题,第一个是关于用于处理大量文件的理想线程数的问题,第二个是如何实现最佳性能。

让我们从第二个问题开始,首先我不会对每个文件进行并行化,但我会同时并行处理一个文件上的处理。 这将有助于您的环境的多个部分: - 硬盘驱动器,因为它不必从一个文件寻找到n - 1个其他 - 操作系统文件缓存将是温暖的,您将需要所有的数据线程,你将不会遇到多少缓存垃圾。

我承认,并行化您的应用程序的代码稍微复杂一点,但您将获得的好处是显着的。

从这个问题的答案很简单,您应该在系统中每个核心最多匹配一个线程。 这将使您尊重您的缓存,并最终在您的系统上实现最佳性能。

最重要的一点是,使用这种类型的处理,您的应用程序将更加尊重您的系统,因为同时访问n个文件可能会使您的操作系统无响应。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM