【问题标题】:Algorithm for distributing workload in a thread pool在线程池中分配工作负载的算法
【发布时间】:2014-12-18 18:23:53
【问题描述】:

假设我们有 T 个线程,我们想将大小为 N 的问题分配给这些线程。每个线程都会选择该问题的一部分来执行它。每个线程将使用 thread_id(从 0 到 T-1 的数字)、T 和 N 来计算子问题的范围。假设子问题的范围是[S, E),其中S和E属于[0, N]。

例如。假设我们有一个整数数组。数组的大小为 10。我们希望将该数组的每个元素增加 1,并且我们希望使用 4 个线程并行执行此操作。

  • thread_id==0 的第一个线程将使用范围 [0, 3)
  • thread_id==1 的第二个线程将使用范围 [3, 6)
  • thread_id==2 的第三个线程将使用范围 [6, 8)
  • thread_id==3 的第 4 个线程将使用范围 [8, 10)

有人知道计算这些范围的快速算法吗?最好没有原子或分支。

【问题讨论】:

  • 实际上,创建线程比任何合理的计算范围慢一千倍。别担心。
  • @MooingDuck 你能详细说明一下吗?我是误读了问题还是遗漏了什么?该操作不要求调度,只是范围的划分。
  • 你对这些确切的范围有多挑剔?如果一个不同的线程的范围稍微小一点,而不是总是最后一个,那可以吗?
  • @ciamej:我的观点是,如果我使用一种速度极慢的算法来计算范围,它不会对整个代码的性能产生可衡量的影响,从而使整个问题变得毫无意义。有点像“嘿,我要把我所有的东西从 A 家搬到 B 家;我应该把闹钟放在一边以减轻体重吗?”
  • @潘。 Christopouls Charit:对于你的例子,如果你得到一个不同的范围,但仍然是一个公平的范围——比如 [0,2)、[2,5)、[5,7)、[7,10),这有关系吗?

标签: multithreading algorithm


【解决方案1】:

如果我理解正确,您是在寻找这样的等式吗?

S = floor(thread_id * N/T)
E = floor((thread_id + 1) * N/T)

如果您先相乘 (threadId * N) 然后再除 (/N),您可以使用整数进行计算,而无需使用 floor 函数。

【讨论】:

  • 它已经在示例中中断了。你会得到 (0,2), (2,5) ... 不过也许就足够了。
  • @luk32 它会产生 [0,2), [2,4), [4,7), [7,10)。它与 op 提出的序列有何不同?
  • 第一。 (1+1)*10/4 对我来说是 5,所以它是 (2,5)。第二。您真的会问,(0,2)与(0,3)有何不同?我不是说这很重要,我是说它不同。我说,也许这种分布就足够了。
  • @luk32 该操作只是要求公平划分范围。当然 [0,2) 与 [0,3) 不同,但这种差异并不重要。你为什么写我的解决方案“中断”?我认为这是完全有效的。
  • 它会中断,因为 OP 给出了预期的输出,而您的方法提供了不同的输出。 “thread_id==0 的第一个线程将使用范围 [0, 3)”。最后一次,我说这可能无关紧要=),也可能无关紧要。我刚才说,这与给定的期望不同。仅此而已。
【解决方案2】:

我认为这两个例子应该可行。所有操作都是整数。除了标明不是的那个。

这个逻辑更简单,但它不会按您的要求分配工作。它会将更大的工作分配给所有工作人员,除了最后一个工作份额会显着降低。这在理论上应该不是问题,因为一名工人的最大工作量保持不变。

items_per_thread = ceil(N/T); // This is not an integer division.
start = thread_id*items_per_thread;
stop = min(start+items_per_thread, N);

这个应该根据你的需要分配工作。

items_per_thread = N/T;
start = thread_id*items_per_thread+min(thread_num, N mod T);
stop = start+items_per_thread;
if(thread_num < N mod T) stop += 1;

我认为不可能避免分支。

感觉很冒险,我在python中做了一个live demo,它也包含了ciamej的方法。:

import math
def distribution1(id ,N, T):
    items_per_thread = math.ceil(N/T);
    start = id*items_per_thread;
    stop = min(start+items_per_thread, N);
    return (start, stop)

def distribution2(id ,N, T):
    items_per_thread = math.floor(N/T);
    start = id*items_per_thread+min(id, N % T);
    stop = start+items_per_thread;
    if(id < N % T): stop += 1;
    return (start, stop)

def distribution3(id ,N, T):
    S = math.floor(id * N/T)
    E = math.floor((id + 1) * N/T)
    return (S,E)

def distribute(N, T, method):
    ret = []
    for i in range(T):
        ret.append(method(i, N, T))
    return ret

N=10
T=4
print(distribute(N, T, distribution1))
print(distribute(N, T, distribution2))
print(distribute(N, T, distribution3))

输出:

[(0, 3), (3, 6), (6, 9), (9, 10)]
[(0, 3), (3, 6), (6, 8), (8, 10)]
[(0, 2), (2, 5), (5, 7), (7, 10)]

【讨论】:

  • double items_per_thread = double(N)/T; int start = int(thread_id*items_per_thread); int stop = int((thread_id+1)*items_per_thread)-1; 使用双重避免分支
  • 我假设一切都是整数。
  • 仅仅因为所有输入和输出都是整数并不意味着中间值必须是。请注意我在double 之间的投射。
  • 哦,我没有看到评论的编辑版本,并假设您想要更正类型。不提供替代方案。你当然是对的。虽然解决方法不行。对于线程 0,停止是int((0+1)*2,5)-1 = 2-1 = 1。我的快速 python 实现给出了这样的序列:[(0, 1), (2, 4), (5, 6), (7, 9)]
  • 我的产生(0,1)(2,4)(5,6)(7,9),不知道你为什么认为这不合适。哦!我的使用包容性界限而不是排斥性,这可能是混乱的根源。只需删除末尾的-1 即可获得独占边界:(0,2)(2,5)(5,7)(7,10)。再说一次,您的评论包含范围,但答案中的代码是排他性的......
猜你喜欢
  • 1970-01-01
  • 2018-01-20
  • 2021-03-15
  • 1970-01-01
  • 2014-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-16
相关资源
最近更新 更多