[英]Difference between the several ways to parallelize nested for loops in C, C++ using OpenMP
我刚刚开始学习使用 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.720 秒减少到并行执行的 5.782 秒。 这是合理的结果,因为我用 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 编写的多线程程序的流程。
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;
由于中间的sum = 0
, collapse(3)
不适用于此循环。
现在是一个细节:
#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++)
{
我希望这能从逻辑的角度对那里发生的事情有所了解。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.