【发布时间】:2009-10-03 10:14:53
【问题描述】:
我的问题是关于如何最好地构建我的 (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 都是“大”的,那么问题是可并行化的,令人尴尬。目前,我有一个线程池,只是将最内层循环的计算扔到池中。问题是,最内层的循环通常是对单例的迭代,因此线程池中只有一个正在运行的线程。我曾考虑过使用期货将值返回到外部循环,但这需要期货等,而且它变得非常混乱(我认为)。
我意识到我上面列出的结构相当复杂,所以我也对一些简化案例感兴趣:
-
a_i(h_i) = a_i;独立于 h_i -
A_i(a_1, ..., a_{i-1}) = A_i;独立于 a_1, ... a_{i-1} -
g_i = 0;独立于 H_{i+1} - 所有外部循环都是“大”的;这些集合中的元素数量远大于核心数量。
现在,在实践中,n
编辑:
清理了第一个伪代码块,使其与另一个一致。 由于人们无法理解我的数学符号,这里有一个更具体的简单示例:
#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 调用放入线程池,并在最内层循环的末尾加入。如上所述,当最内层循环仅迭代一个元素时,这不起作用。这实际上并不需要对现有代码进行重大重组,如果可能的话,我想避免它。
【问题讨论】:
标签: multithreading parallel-processing