我有一个while
循环,想使用OpenMP在2个线程上并行化它。循环中的变量不依赖于先前迭代中的值,因此我认为必须有某种使其并行化的方法。我有2个线程,所以每次循环同时进行2次while循环,每个循环执行自己的计算。此循环的目标是找到alfa
的值,这是共轭梯度法中用于寻找最佳点的步长。
我想我必须以某种方式利用alfa
,alfaIter
变量和OpenMP语句来使此并行循环工作,但不知道如何工作。
#pragma omp parallel num_threads(2)
{
while (alfaSet == false) {
alfaIter++;
alfa = pow(gamma, alfaIter - 1);
b = 0;
for (i = 0; i < dim; i++) {
testX[i] = x[i] + alfa * d[i];
}
for (i = 0; i < dim; i++) {
b += d[i] * g[i];
}
if (shanno(testX, dim) - shanno(x, dim) <= delta * alfa * b) {
alfaIter = 0;
alfaSet = true;
}
}
}
编辑1:这种实现似乎还可以:
#pragma omp parallel num_threads(alfaThreads)
{
int alfaIter = omp_get_num_threads();
int step = omp_get_num_threads();
double localAlfa = alfa;
double *testX = (double *) malloc(dim * sizeof(double));
while (!alfaSet) {
#pragma omp barrier
alfaIter += step;
localAlfa = pow(gamma, alfaIter - 1);
for (i = 0; i < dim; i++) {
testX[i] = x[i] + localAlfa * d[i];
}
if (func(testX, dim) - delta * localAlfa * b <= oldFunc) {
#pragma omp critical
{
if (!alfaSet) {
alfaSet = true;
alfaIter = 0;
alfa = localAlfa;
}
}
}
}
free(testX);
}
因此,在使用这段代码一段时间后,我发现没有任何同步,因此线程不再彼此等待,并且它们以不可预测的方式到达了代码的各个部分。 OpenMP barrier现在可以同步它们,并且我总是得到相同的迭代次数以及性能增益。但是,有时程序现在会崩溃。僵局?如何检查导致崩溃的原因以及如何防止崩溃?
这是算法的全部实现:https://gist.github.com/mazury/394adc82ab51ce36acfae297ae0555ce
答案 0 :(得分:0)
#pragma omp parallel
在多个线程上并行运行以下代码。因此,几个循环将同时运行。所有这些版本都将获取全局变量并同时或多或少地更新它们,而您不能简单地控制会发生什么。
例如,很可能以不受控制的方式修改了alfaIter,从而导致未定义的行为。
这是处理器的第一行代码如何执行
1 read alfaIter in local var (register)
2 var++
3 write register var in alfaIter
4 fetch alfaIter to call pow and put it in stack or register
5 call pow(...)
让我们说这些指令是线程A中的1a 2a 3a 4a 5a和线程B中的1b 2b 3b 4b 5b。
现在执行的实际顺序是什么?
假设是
1a 2a 3a 4a 5a 1b 2b 3b 4b 5b.
行为将符合预期。 Pow在线程A中使用alfaIter = 1调用,在线程B中使用alfaIter = 2
调用但是其他排序可能导致不同的行为
例如
1a 1b (both local regs in thrd A and B have initial value of 0)
2a 3a (alfaIter=1 written back to memory by thead A)
2b 3b (alfaIter=1 written back to memory by thead B)
4a 4b 5a 5c (pow called by both threads with the same value of alfaIter=1)
由于任何排序都是可能的,因此循环的行为是不可预测的。
使其可预测的解决方案是通过原子操作实现的。 在这种情况下,您可以确保对内存的访问是顺序的,并且while循环的行为将符合预期。
但这有一个主要缺点。原子操作非常长,在现代处理器上通常需要约100个周期。这将大大降低您的代码速度,并使它的速度比顺序版本慢很多。
通常,最好的方法是使用for循环,但您似乎无法使用。
我建议的是渲染大多数var局部变量,运行将alfaIter递增2(或按线程数增加)的并行线程,并仅将全局操作用于终止条件。
示例代码:
#pragma omp parallel num_threads(2)
{
int alfaIter=omp_get_thread_num();
int step=omp_get_num_threads();
float alfa;
float testX[dim],b;
// and maybe d[] and g[] but I do not understand what they do
while (alfaSet == false) { // no problem to read a global var
alfaIter+=step;
alfa = pow(gamma, alfaIter - 1);
b = 0;
for (i = 0; i < dim; i++) {
testX[i] = x[i] + alfa * d[i];
}
for (i = 0; i < dim; i++) {
b += d[i] * g[i];
}
if (shanno(testX, dim) - shanno(x, dim) <= delta * alfa * b) {
#pragma omp critical
if (! alfaSet) { // you can do safe operations here
alfaIter = 0;
alfaSet = true;
}
}
}
}
未经测试,但可以作为起点。