二次算法的并行化

时间:2011-09-29 09:32:41

标签: algorithm parallel-processing

假设我有一个N个元素的向量(数组,列表,等等......)V,假设V 0 到V (N-1)。对于每个元素V i ,需要为每个索引j计算函数f(V i ,V j )(包括情况i) = j)的。该函数是对称的,因此一旦计算出f(V i ,V j ),就不需要重新计算f(V j , V <子> I )。然后我们对函数进行N(N + 1)/ 2次总评估,使其成为O(N 2 )算法。让我们假设计算f所花费的时间相对较长但是一致。

现在,我想并行执行算法。我需要确定(某些M个)工作线程的调度,以便两个线程不会同时使用相同的内存部分(即相同的元素)。例如,f(V 1 ,V 2 )可以与f并行求值(V 3 ,V 4 ),但不与f平行(V 2 ,V 3 )。工作流程分为几个步骤,每个步骤,每个工作线程执行一次f的评估。然后线程被同步,之后它们继续进行下一步,依此类推。

问题是,我如何确定(最好是最佳)每个线程的时间表作为一系列索引对(i,j),以便解决完整的问题(即每个索引对只访问一次,考虑到对称性)?虽然直接的答案当然是好的,但我也很欣赏指向算法甚至相关网站/文献的指针。

3 个答案:

答案 0 :(得分:5)

这让我想起了一个常见的体育赛程安排问题:在N队的联赛中,安排N-1个比赛日,这样每个队伍每天都有一场比赛,每场比赛一次。

下棋,这个问题有一个非常具有说明性的解决方案。将所有电路板并排排列在长桌上。一名球员总是在同一把椅子上。其他玩家在跳过该玩家的同一方向围绕桌子旋转。

答案 1 :(得分:0)

让我们看看直接实施:

for(i=0; i < N; ++i)
{
    for(j=1; j < i; ++J)
    {
        compute i,j pair and assign to i,j, and j,i result
    }
}

我是C ++程序员,所以我可以考虑使用OpenMP进行外部循环并行化:

#pragma OMP parallel for
for(i=0; i < N; ++i)
{
    ....
}

如果你不知道OpenMP,它只是根据处理器的数量将循环划分为n个循环,并且并行执行它们。我不认为在这种情况下结果会很好,因为每个i + 1循环都比i循环短。让我们通过这种方式编写这个算法,所有循环都具有相同的长度。循环总数将为N / 2。第一个循环处理0和N-1行。第二个循环处理1和N-2行等。这样的循环可以成功并行化,没有冲突。简化代码,没有关于偶数/奇数N等的详细信息:

#pragma OMP parallel for
for(i=0; i < N/2; ++i)
{
    Handle i value
    ...

    Handle N - 1 - i value
    ...
}

这只是一般性的想法,你可以解决不正确的细节。

答案 2 :(得分:0)

我看到两种可能性。 一种是在先前的M个线程中划分K = N(N + 1)/ 2个任务。以下只是伪代码:

allocateTasks() {
    num = ceil(N*(N+1)/2/M); // number of tasks per thread
    i0 = 0, j0 = 0;
    n = 0; 
    for (i = 0; i < N; ++i) {
        for (j = i; j < N; ++j) { // skip symmetries
            if (n == 0) {i0 = i; j0 = j;}
            ++n;
            if (n == num) {
                thread(i0, j0, num); 
                n = 0; 
            }
        }
    }
    if (n > 0) thread(i0, j0, n); 
}

thread(i0, j0, num) {
    // perform num tasks starting at (i0, j0)
    i = i0; j = j0;
    for (n = 0; n < num; ++n) {
        // f[i, j] = ...
        ++j;
        if (j >= N) {++i; j = i;}
    }
}

这里的问题是当f(i,j)的计算不同时, 那么线程之间的负载就不平衡了。

所以,也许第二种方法更好。每个线程都使用下一个线程 任务。没有全局预分配任务。

// init boolean array: done[0..N-1, 0..N-1] = false
thread() {
    for (i = 0; i < N; ++i) {
        for (j = i; j < N; ++j) { // skip symmetries
            boolean mytask = false;
            lock(mutex); // synchronize access to global array done[][]
            if (!done[i, j]) {
                done[i, j] = true;
                mytask = true;
            }
            unlock(mutex);
            if (mytask) {
                f(i, j) = ...
            }
        }
     }
}

在任何情况下,访问f(i,j)都将通过

g(i, j) = j >= i ? f(i, j) : f(j, i)