我必须使用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中,但我无法经常测试它,因为它是一个远程且无法访问的服务器。
答案 0 :(得分:13)
根据Herb Sutter在his article中讨论的Amdahl定律:
程序的一些处理完全是“O(N)”可并行化的(称为p部分),只有那部分可以直接在具有越来越多处理器核心的机器上扩展。该程序的其余工作是“O(1)”顺序。 [1,2]假设完全使用所有可用内核而没有并行化开销,Amdahl定律表示在具有N个内核的机器上实现该程序工作负载的最佳加速由
给出
在您的情况下,I / O操作可能占用大部分时间,以及同步问题。您可以计算阻塞(?)慢速I / O操作所需的时间,并大致找到适合您任务的线程数。
可以找到Herb Sutter的并发相关文章的完整列表here。
答案 1 :(得分:11)
我对HP / UX不太了解,但在Windows世界中,我们使用线程池来解决这类问题。雷蒙德陈wrote about this一会儿回来,事实上......
如果线程数超过系统中CPU核心数的2倍,我通常不会期望在CPU绑定负载上能够很好地扩展。对于I / O绑定负载,您可能能够获得更多,具体取决于您的磁盘子系统的速度,但是一旦达到大约100左右,我会认真考虑更改模型......
答案 2 :(得分:6)
详细说明它真的取决于
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的闪存阵列上,那么解决方案与它们驻留在单个磁盘上的方式不同(如果“全局结构插入”占用的内容少于读取问题的话{{3}有限,你可能还有一个2级管道,有2个线程 - 读取阶段为插入阶段提供。)
但在这两种情况下,架构可能都是一个2阶段的垂直管道。 n读取线程和m写入线程,其中n和m由阶段的“自然并发”决定。
为每个文件创建一个线程可能会导致磁盘颠簸。就像你将CPU绑定进程的线程数量定制为自然可实现的CPU并发(并且高于创建上下文切换开销AKA颠簸)在I / O端也是如此 - 从某种意义上说,你可以想到磁盘颠簸为“在磁盘上进行上下文切换”。
答案 3 :(得分:6)
你说文件都在一个目录中。这是否意味着他们都在一个物理驱动器上?
如果是这样,并假设它们尚未缓存,那么你的工作就是保持单个读头忙,没有多少线程可以帮助它。事实上,如果由于并行性而必须在曲目之间跳跃,你可以放慢速度。
另一方面,如果计算部分花费大量时间,导致读头必须等待,那么有> 1个线程可能是有意义的。
通常情况下,使用线程来提高性能是没有意义的,除非它允许您同时使并行的硬件工作。
更常见的是,线程的价值在于,例如,跟踪多个同时进行的对话,例如,如果您有多个用户,每个线程可以等待自己的Johny或Suzy而不会感到困惑。
答案 4 :(得分:5)
如果工作负载听起来像I/O那么接近,那么你可能会获得最大吞吐量,并且线程数与你的主轴一样多。如果您有多个磁盘并且所有数据都在同一个RAID 0上,那么您可能不需要任何多个线程。如果多个线程试图访问磁盘的非顺序部分,操作系统必须停止读取一个文件,即使它可能正好在头部下方,并移动到磁盘的另一部分以服务另一个线程,因此它没有饿死。只有一个线程,磁盘需要永远不会停止读取以移动磁头。
显然,这取决于访问模式是非常线性的(例如视频重新编码),而且数据实际上是在磁盘上未碎片化的,这取决于很多。如果工作负载更多的是CPU限制,那么它就没那么重要了,你可以使用更多的线程,因为无论如何磁盘都会翻转它的拇指。
正如其他海报所说,首先介绍一下!
答案 5 :(得分:4)
不要听起来陈词滥调,但你可以根据需要使用尽可能多的线程。
基本上,您可以根据(实际)完成时间绘制线程数的图表。您还可以绘制一个总线程到总线程时间。
第一个图表将帮助您确定CPU功率瓶颈的位置。在某些时候,您将成为I/O绑定(意味着磁盘无法足够快地加载数据)或线程数量将变得如此之大,这将影响机器的性能。
第二次确实发生了。我看到一段代码最终创建了30,000多个线程。通过将其限制为1,000,它最终变得更快。
另一种看待这个问题的方法是:速度有多快? I / O成为瓶颈的地方是一回事,但你可能会在那之前达到“足够快”的地步。
答案 6 :(得分:4)
使用线程池而不是为每个文件创建线程。编写解决方案后,您可以轻松调整线程数。如果作业彼此不相关,我会说线程数应该等于核心数/ cpus。
答案 7 :(得分:4)
答案在某种程度上取决于您需要对每个文件执行的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核心数量,可能会获得最佳吞吐量。是否值得努力实现更复杂的设置,这取决于特定问题中瓶颈的位置。
答案 8 :(得分:3)
这听起来可能有点太老了,但你考虑过简单的分叉过程吗?听起来你有高度独立的工作单位,返回数据的聚合很少。流程模型还可以释放虚拟地址空间(如果您使用的是32位计算机,可能会很紧张),允许每个工作室对正在处理的整个文件说mmap()。
答案 9 :(得分:3)
有很多变量会影响性能(操作系统,文件系统,硬盘速度与CPU速度,数据访问模式,读取后对数据进行的处理等)。
所以你最好的办法就是在一个有代表性的数据集上尽可能地尝试每个可能的线程计数的测试运行(如果可能的话,一个大的数据集,这样文件系统缓存不会太严重地扭曲结果),并记录多长时间它需要每次。从单个线程开始,然后使用两个线程再次尝试,依此类推,直到您觉得有足够的数据点为止。最后,您应该将数据绘制成一条漂亮的曲线,以指示“最佳位置”的位置。您应该能够循环执行此操作,以便在一夜之间自动编译结果。
答案 10 :(得分:2)
我同意所有建议线程池的人:您使用池计划任务,并且池分配线程来执行任务。
如果您受CPU限制,只要CPU使用率低于100%,就可以继续添加线程。当你I/O绑定时,磁盘颠簸可能会在某些时候阻止更多线程提高速度。你必须自己找出来。
你见过英特尔的Threading Building Blocks吗?请注意,我无法评论这是否是您所需要的。我在Windows上制作了一个小玩具项目,那是几年前的事。 (它有点类似于你的,BTW:它以递归方式遍历文件夹层次结构并计算它找到的源代码文件中的行。)
答案 11 :(得分:2)
更多线程不一定会为您提供更高的吞吐量。线程具有非常重要的成本,无论是创建(在CPU时间和OS资源方面)还是运行(在内存和调度方面)。而且你拥有的线程越多,与其他线程争用的可能性就越大。添加线程有时甚至会降低执行速度。每个问题都略有不同,你最好写一个漂亮,灵活的解决方案并试验参数,看看哪个最好。
您的示例代码,为每个文件生成一个线程,几乎会立即使系统中的max_threads
值超过10左右。正如其他人所建议的那样,带有工作队列的线程池就是您可能想要的。每个文件都是独立的这一事实很好,因为它使几乎令人尴尬地平行(除了每个工作单元末尾的聚合)。
会影响吞吐量的一些因素:
去年我写了一个与你描述的基本相同的应用程序。我最终使用了Python和pprocess
library。它使用了一个带有工作进程池的多进程模型,通过管道(而不是线程)进行通信。主进程将读取工作队列,将输入切换为块,并将块信息发送给工作人员。工作人员会处理数据,收集统计信息,并在完成后将结果发送回主人。主人将结果与全局总数相结合,并向工人发送另一个块。我发现它几乎线性地扩展到8个工作线程(在8核盒子上,这是相当不错的),除此之外它降级了。
需要考虑的一些事项:
mmap()
(或等效内容)对内存映射输入文件进行评估,但仅在您对基线案例进行概要分析后当您描述的一个目录中有大量文件时,除了可能达到文件系统限制之外,还需要时间来统计目录并找出您已经处理过哪些文件以及哪些文件仍需要升级显著。例如,考虑按日期将文件分解为子目录。
关于性能分析的另一个词:在将小型测试数据集的性能外推到超大型数据集时要小心。你不能。我发现了一个很难的方法,你可以达到某一点,我们在编程中每天对资源的定期假设不再适用。例如,我发现当我的应用程序超越它时,MySQL中的语句缓冲区是16MB!保持8个内核忙可以占用大量内存,但如果你不小心,你可以轻松地咀嚼2GB内存!在某些时候,你必须测试生产系统上的真实数据,但是给自己一个安全的测试沙箱来运行,所以你不要生产生产数据或文件。
与此讨论直接相关的是Tim Bray博客上的一系列文章,名为"Wide Finder" project。问题只是解析日志文件并生成一些简单的统计信息,但在多核系统上以最快的方式。许多人以各种语言提供解决方案。绝对值得一读。
答案 12 :(得分:1)
最简单的线程有多昂贵取决于操作系统(您可能还需要调整一些操作系统参数以超过一定数量的线程)。至少每个都有自己的CPU状态(寄存器/标志,包括浮点)和堆栈以及任何特定于线程的堆存储。
如果每个单独的线程不需要太多不同的状态,那么你可以通过使用小的堆栈大小来使它们相当便宜。
在极限情况下,您可能最终需要使用非操作系统协作线程机制,甚至使用微小的“执行上下文”对象自行复用事件。
刚开始使用线程并稍后担心:)
答案 13 :(得分:1)
作为一个球场号码,您应该将线程数保持在10到100之间,以最大限度地减少锁争用和上下文切换开销。
答案 14 :(得分:0)
这里有两个问题,第一个是关于理想的线程数用于处理大量文件的问题,第二个问题是如何实现最佳性能。
让我们从第二个问题开始,首先我不会对每个文件进行并行化,但我会同时并行处理一个文件上的处理。这将有助于您的环境的多个部分: - 硬盘驱动器,因为它不必从一个文件中寻找到n - 1个其他文件 - 操作系统文件缓存将使用您在所有线程上需要的数据而变得温暖,并且您将不会遇到高速缓存垃圾。
我承认并行化您的应用程序的代码稍微复杂一点,但您获得的好处是显着的。
从这个问题的答案很简单,您应该在系统中每个核心最多匹配一个线程。这将使您能够尊重您的缓存,并最终在您的系统上实现最佳性能。
最重要的一点是,使用这种类型的处理,您的应用程序将更加尊重您的系统,因为同时访问n个文件可能会使您的操作系统无响应。