在 C 中并行化嵌套循环的几种方法之间的差异,C++使用 OpenMP
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
指令的位置,它会构造平行区域N2时间。当我使用 linux 时间命令时,我通过用户时间的巨大增加找到了它。
下次,我尝试了下面的代码,这也有效。
#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.782 s。这是合理的结果,因为我用 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, %dn", 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++)
{
我希望这能从逻辑的角度阐明那里发生的事情。
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- 不同的类或结构初始化方法之间的性能差异是什么?
- #include < conio.h> 和 getch() 方法之间的关系是什么?
- 是否有一种标准方法来计算两个 asctime() 值之间的天数
- 在C++事务之间存储大量字符数据的有效方法
- 定义类模板构造函数的两种方法之间的区别
- std::list 的两个 insert() 方法签名之间的实现差异
- 使瓷砖之间没有边框的最佳方法?
- 两个类之间的共享方法
- 在C++和Python之间交换数据的最快方法是什么?
- 计算两个uint8_t变量之间差值的最快方法是什么?
- 在 c++ 中,在线程之间共享数据容器的最佳方法是什么?
- 获取字符串大小的各种方法之间的比较
- 在 C 中并行化嵌套循环的几种方法之间的差异,C++使用 OpenMP
- 在两个Raspberry Pis C 之间发送重复命令的方法
- 分流场算法C++方法之间未正确传递?
- 根据编译时条件在类型之间选择类型的惯用方法
- C++对象构造方法之间的差异
- 有没有一种方法可以在基于枚举的可变参数模板函数之间进行选择,这比将函数包装在结构中更简单
- 在QTcpSocket和python套接字之间交换数据的正确方法是什么?