使用依赖项并行化嵌套循环

时间:2009-10-03 00:25:01

标签: multithreading parallel-processing

我的问题是如何最好地构建我的(C ++)代码以支持并行化耗时的计算。有问题的伪代码具有以下结构:

for a_1(y_1) in A_1
    for a_2(y_2) in A_2(a_1)
        ...
            for a_n(y_n) in A_n(a_1, ..., a_{n-1})
                y_n = f(a_1, ..., a_n)
            y_{n-1} = g_n(Y_n)
        ...
    y_1 = g_2(Y_2)
.

粗略地说,每个循环遍历集合A_i中的元素,其连续元素依赖于先前迭代的反馈y_i。换句话说,要确定下一个a_i,我们必须完成当前a_i的所有计算。此外,内部集合取决于外部迭代。以递归形式编写:

Iterate(A_i, a_1, ..., a_{i-1}):
    for a_i(h_i) in A_i
        Y_i += Iterate(A_{i+1}, a_1, ..., a_i)
    return g(Y_i)

Iterate(any, a_1, ..., a_n):
    return f(a_1, ..., a_n)

Iterate(A_1)

假设f(...)是耗时的计算,并且反馈函数g(...)是简单的(快速)。现在,如果所有集合A_i都是“大”,那么问题就是令人尴尬的可并行化。目前,我有一个线程池,只是将最内层循环的计算放入池中。问题是,最内层循环通常是对单例的迭代,因此线程池中只有一个正在运行的线程。我曾考虑使用期货将价值返回到外部循环,但这需要期货期货等,而且它会变得非常混乱(我认为)。

我意识到我上面列出的结构非常复杂,所以我也感兴趣的是一些简化的案例:

  1. a_i(h_i) = a_i;独立于h_i
  2. A_i(a_1, ..., a_{i-1}) = A_i;独立于a_1,... a_ {i-1}
  3. g_i = 0;独立于H_ {i + 1}
  4. 所有外环都是“大”的;这些集合中的元素数量远远大于核心数量。
  5. 现在,实际上,n <= 3,并且项目1适用于所有外部循环,而项目2-4都保持不变,因此针对该情况的特定解决方案就足够了。但由于我在这里提出这个问题,我很想知道如果可能的话,如何处理更多一般性问题的额外复杂性。


    修改

    清理第一个伪代码块以使其与另一个伪代码块保持一致。 由于人们无法理解我的数学符号,这里有一个更具体的简单例子:

    #include <cmath>
    #include <iostream>
    #include <vector>
    using namespace std;
    
    double f(double a1, int a2, double a3){ // Very slow function
        cout << a1 << ", " << a2 << ", " << a3 << endl;
        return pow(a1*a3, a2) + a1 + a2 + a3; // just some contrived example
    }
    
    int g2(const vector<double> &Y3){ // average-ish
        double sum = 0;
        for(int i = 0; i < Y3.size(); ++i){ sum += Y3[i]; }
        return int(sum / (Y3.size() + 1));
    }
    
    double g1(const vector<int> &Y2){ // return 1/(min(Y2)+1.0)
        int imin = 0; int minval = 0;
        for(int i = 1; i < Y2.size(); ++i){
            if(Y2[i] < minval){
                imin = i;
                minval = Y2[imin];
            }
        }
        return 1.0/double(minval+1.0);
    }
    
    int main(){
        for(double a1 = 0.0, h1 = 10.0; a1 < 1.0; a1 += h1){ // for a1 in A1
            vector<int> Y2;
            for(int a2 = 2, h2 = 1; a2 <= (int)(5*a1+10); a2 += h2){ // for a2 in A2(a1)
                vector<double> Y3;
                for(double a3 = 6.0, h3 = 1.0; a3 >= (a1+a2); a3 -= 0.5*h3){ // for a3 in A2(a1, a2)
                    h3 = f(a1, a2, a3);
                    Y3.push_back(h3);
                }
                h2 = g2(Y3);
                Y2.push_back(h2);
            }
            h1 = g1(Y2);
        }
    
        return 0;
    }
    

    我随机选择了值,结果f仅被评估了3次。请注意,上面的代码不可并行化。 假设可以查询循环的增量是否依赖于更高的循环。

    我应该澄清我所追求的东西。当我最初说结构时,我或许应该说并行化方法或类似的东西。例如,我第一次尝试并行化是将最内部调用f抛出到一个线程池中,并在最内层循环的末尾加入。如上所述,当最内层循环仅在一个元素上迭代时,这不起作用。这并不需要显着重构现有代码,如果可能的话我想避免使用它。

3 个答案:

答案 0 :(得分:2)

您可以尝试以map-reduce问题(http://en.wikipedia.org/wiki/MapReduce)的形式表达您的问题,使每个级别的嵌套都成为一个map-reduce作业。 for循环将转换为映射,g_i调用简化步骤。

您可以尝试使您的伪语言更清晰......也许表达为n = 3或n = 4的python程序?你的“for”是一个普通的for循环吗?如果是这样,我真的不明白第一对括号。

我不确定您的问题是否可以按照声明的形式进行并行化。如果你说循环的变量取决于之前的迭代,那么它看起来更像是一个顺序问题。

答案 1 :(得分:0)

说实话,乍一看你的记谱很难(至少对我而言)。也许你可能更明确或可能使用C或C ++代码。你的并行化方法是什么(pthreads,openmp等)?我怀疑一个问题是你可以改善你的负载平衡。例如,您可能不希望将工作分配给卡交易方式中的线程。

答案 2 :(得分:0)

如果可能的话,加速这类深层嵌套调用的最佳方法就是没有深层嵌套的调用。

您通常可以重新组织数据或数据中的链接,以获得可能为您提供循环级别的引用,或者您有时可以找到一种方法来依次排列循环,存储中间信息。有时它甚至会创建一个不同的对象结构。

我并不是说这总是有效,但即使删除一个级别,也会比你可能尝试的其他任何事情都显着减少。

如果我能理解你的伪代码,我可以尝试一下,但我猜你已经抽出了大部分结构,无论如何都需要有洞察力的设计。