我刚刚开始研究OpenMP的并行编程,并且在嵌套循环中有一个微妙的地方。我编写了一个简单的矩阵乘法代码,并检查了结果是否正确。但是实际上有几种方法可以并行化此for循环,就底层细节而言可能有所不同,我想问一下。
首先,我在下面编写了将两个矩阵A,B相乘并将结果分配给C的代码。
for(i = 0; i < N; i++)
{
for(j = 0; j < N; j++)
{
sum = 0;
#pragma omp parallel for reduction(+:sum)
for(k = 0; k < N; k++)
{
sum += A[i][k]*B[k][j];
}
C[i][j] = sum;
}
}
它可以工作,但是要花很长时间。而且我发现由于parallel
指令的位置,它将构造并行区域N 2 时间。当我使用linux time命令时,是由于用户时间的大量增加而发现的。
下次,我尝试了下面的代码,该代码也有效。
#pragma omp parallel for private(i, j, k, sum)
for(i = 0; i < N; i++)
{
for(j = 0; j < N; j++)
{
sum = 0;
for(k = 0; k < N; k++)
{
sum += A[i][k]*B[k][j];
}
C[i][j] = sum;
}
}
使用上面的代码,运行时间从顺序执行的72.720s减少到并行执行的5.782s。这是合理的结果,因为我执行了16个内核。
但是我不容易想到第二个代码的流程。我知道,如果我们私有化所有循环变量,则程序会将嵌套循环视为大小为N 3 的一个大循环。通过执行以下代码,可以轻松地对其进行检查。
#pragma omp parallel for private(i, j, k)
for(i = 0; i < N; i++)
{
for(j = 0; j < N; j++)
{
for(k = 0; k < N; k++)
{
printf("%d, %d, %d\n", i, j, k);
}
}
}
printf
执行了N 3 次。
但是在我的第二个矩阵乘法代码中,最里面的循环前后有sum
。而且它使我很容易在脑海中展开循环。我写的第三个代码在我的脑海中很容易展现。
总而言之,我想知道第二矩阵乘法代码在幕后真正发生的情况,尤其是随着sum
值的变化。或者,我真的要感谢您对工具的一些推荐,以观察用OpenMP编写的多线程程序的流程。
答案 0 :(得分:1)
omp for
默认仅适用于下一个直接循环。内部循环完全不受影响。这意味着,您可以这样考虑第二个版本:
// Example for two threads
with one thread execute
{
// declare private variables "locally"
int i, j, k;
for(i = 0; i < N / 2; i++) // loop range changed
{
for(j = 0; j < N; j++)
{
sum = 0;
for(k = 0; k < N; k++)
{
sum += A[i][k]*B[k][j];
}
C[i][j] = sum;
}
}
}
with the other thread execute
{
// declare private variables "locally"
int i, j, k;
for(i = N / 2; i < N; i++) // loop range changed
{
for(j = 0; j < N; j++)
{
sum = 0;
for(k = 0; k < N; k++)
{
sum += A[i][k]*B[k][j];
}
C[i][j] = sum;
}
}
}
您可以通过尽可能在本地声明变量来简单地使用OpenMP进行变量的所有推理。即代替显式声明,请使用:
#pragma omp parallel for
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
{
int sum = 0;
for(int k = 0; k < N; k++)
{
sum += A[i][k]*B[k][j];
}
C[i][j] = sum;
}
}
这样,您可以更轻松地使用变量的私有范围。
在某些情况下,将并行应用于多个循环可能会有所帮助。
这是通过使用collapse
来完成的,即
#pragma omp parallel for collapse(2)
for(int i = 0; i < N; i++)
{
for(int j = 0; j < N; j++)
您可以想象这可以像这样进行转换:
#pragma omp parallel for
for (int ij = 0; ij < N * N; ij++)
{
int i = ij / N;
int j = ij % N;
由于collapse(3)
之间存在sum = 0
,因此#pragma omp parallel for
不能在此循环中工作。
现在再详细一点:
#pragma omp parallel
#pragma omp for
是
的简写#pragma omp parallel
for(int i = 0; i < N; i++)
{
#pragma omp for
for(int j = 0; j < N; j++)
{
第一个创建线程-第二个创建线程到所有线程都共享一个循环。这对于现在的理解可能并不重要,但是在某些用例中,这很重要。例如,您可以写:
{{1}}
我希望这从逻辑的角度阐明那里发生的事情。