线程间负载均衡的启发式算法

8
我正在开发一个多线程程序,其中有许多工作线程执行长度不同的任务。我想要负载均衡这些任务,以确保它们完成大致相同的工作量。对于每个任务Ti,我有一个数字ci,它提供了该任务所需工作量的良好近似值。
我正在寻找一种高效(O(N)N =任务数或更好)的算法,它将根据ci的值“大致”平衡负载。它不必是最优的,但我希望能够对结果分配的糟糕程度有一些理论界限。
有什么想法吗?

任务集合是已知的还是随着操作会不断增加?您是否需要担心饥饿问题(例如,具有高 c_i 的任务永远不会运行,如果低 c_i 任务持续添加)? - David Gelhar
@David:任务的数量和其持续时间的估计将会事先确定。这里不涉及饥饿问题。基本上,我的目标是尽量减少执行的总时间。 - Il-Bhima
6个回答

7
你想要实现一个工作窃取算法。每个工作线程都有一个双端队列,新任务添加到最小队列的底部。工作者从自己队列的顶部删除任务(顶部/底部分离减少争用),当工作者没有更多的工作要做时,它会从最大队列的底部窃取一个工作。这很简单,而且效果很好,我认为这就是.net4.0附带的Microsoft并行系统所基于的算法。

结果分配非常好,只有当整个系统中没有更多的任务可用时,工作线程才会没有工作要做。

Nb.如果您想要一些示例代码进行拆解,我的朋友为C#编写了一个工作窃取系统,您可以在此处找到。


1
这是我采用的解决方案。目前正在考虑将我的代码迁移到Cilk,它提供了一个工作窃取调度程序。 - Il-Bhima
哇,看起来是一种非常有趣的编程语言。很高兴我能帮忙 :) - Martin

3

我的倾向是不提前想好如何分配任务,而是将它们全部扔进一个共同的工作队列中。任何没有其他任务要做的工作线程都会从队列中获取下一个任务并完成工作,然后检查队列是否有下一个任务。


如果有很多线程,您可能会得到+1,但在共享任务池上可能会出现严重的锁争用。系统应该被调整以确保线程不会不断地等待锁来获取新任务。这可以通过使任务足够大或让线程一次获取多个任务来实现。ParallelFx库甚至进一步拥有全局和本地工作池,并将“工作窃取”混合其中:http://en.wikipedia.org/wiki/Parallel_Extensions - Wim Coenen
这正是我现在正在做的事情,但每个任务执行一个线程将会产生一些非常重要的开销,因为线程终止并重新分配任务。如果我找不到更快的解决方案,那么这就是我要采取的方法,但基本上我正在尝试找到一种预先分配> 1个任务给每个线程的方法。 - Il-Bhima
@Wim:你是否有争用取决于你使用的锁原语(并且仍然可能比尝试将任务调度到特定线程要便宜得多)。如果你使用一个计数为队列中任务数量的信号量,你只会唤醒足够的线程。你也可以使用无锁队列。如果你有大量的线程和大量的任务,你可以使用n个队列来减少争用,并以轮询方式将任务分配给队列。 - Adrian McCarthy
@Il-Bhima:为每个任务启动一个线程确实会产生很多开销。这就是为什么我有一个固定的线程池,它们会不断返回队列以获取另一个任务。 - Adrian McCarthy
是的,我的意思是我有一个线程池,它会在计数信号量上阻塞,每个线程完成任务后会立即选择另一个任务。你让我真的开始怀疑是否有任何调度算法比你所说的做法更好。 - Il-Bhima

2

最简单的方法是按p_i降序排序作业(但这是O(n log n)),并执行以下操作:

  1. 对于每个线程,我们有估计运行时间e_n = 0。
  2. 对于每个任务i,找到具有最小e_n的线程,在其上排队任务并e_n = e_n + p_i。

该算法应该给出最佳结果,但时间复杂度为O(NM),其中N是任务数,M是线程数。解决方案的总成本为O(N log N + NM),因此对于M << N为O(N log N),对于M接近N为O(n^2)。


1

在O(N)中,这似乎很容易。

给每个线程一些“点数”。设p_i为分配给线程T_i的点数。对于每个任务,选择具有最高p_i的线程,并从p_i中减去任务成本。然后,您只需要跟踪按得分排序的线程,在O(N)时间内是微不足道的,并且可以使用平衡树在O(log N)内轻松完成。

对于连续操作,p_i没有最小值。如果要避免得分朝着-inf走向狂野,请定期向所有得分添加任意数量P(所有得分相同)。

编辑:我弄错了N。上面,N是线程数,与所提出的问题相反。当N =任务数,T =线程数时,这导致O(N*log T)成本。如果T“较小”,则接近O(N)。

编辑2: 如果所有任务和线程数量都已知,则我认为计算最佳调度类似于背包问题,并且在所有情况下都是NP完全的(因此您将在某些地方得到指数级)。正如我上面所描述的那样,简单的基于成本的分析将为您提供相对较好的近似值,只要所有单个任务与分配给每个线程的总成本相比具有较小的成本。


这听起来有趣而且出乎意料地简单。我会考虑一下并回复你。 - Il-Bhima


1

虽然关于背包问题的建议很有帮助,但你说你正在尝试最小化执行的净时间。采用背包方法需要不断增加背包的大小,直到获得可行解-效率不高。

如果并行工作的所有线程中最长完成时间限制了执行的净时间,我想分配任务以使所有线程的最大工作时间最小化。这样做可能会导致一个或多个线程没有做太多的工作,因此我们并没有真正“平衡”工作量。如果您想平衡工作量,那么这就是不同的目标函数。例如,您可能希望最小化线程之间的工作差异。

请查看作业车间调度领域。如果您只偶尔这样做,我建议使用遗传算法-如果您需要经常进行自动化处理,则建议进行一些确定性算法的文献搜索。希望这可以帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接