c++ - 使用 OpenMP 在 C、C++ 中并行化嵌套 for 循环的几种方法之间的差异

标签 c++ multithreading 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 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 核执行的。

但是第二段代码的流程在我的脑海中并不容易画出来。我知道,如果我们将所有循环变量私有(private)化,程序就会将该嵌套循环视为一个大小为 N3 的大循环。通过执行下面的代码可以轻松检查它。

#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 被执行了 N3 次。

但是在我的第二个矩阵乘法代码中,最内层循环之前和之后都有 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 对变量进行所有推理。 IE。而不是显式声明使用:

#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;
    }
}

这样您就可以更轻松地获得变量的私有(private)范围。

在某些情况下,将并行性应用于多个循环可能是有益的。 这是通过使用 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 = 0collapse(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++)
    {

我希望这能够从逻辑的角度解释那里发生的事情。

关于c++ - 使用 OpenMP 在 C、C++ 中并行化嵌套 for 循环的几种方法之间的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57071162/

相关文章:

c++ - 使用 Eclipse IDE 单步执行程序

c++ - 访问成员函数时遇到问题

c++ - 对于 ~95% 写入/5% 读取线程安全的 unordered_map 是否有简单的解决方案?

java - 具有无限循环阻塞 gui 更新 Netbeans 的独立线程

c++ - 如何在通过 nvcc 传递到 VS2015 的文件上启用 OMP?

c++ - 使用 OpenMP 需要多少工作量才开始有意义?

c++ - 自定义迭代器返回 std::pair 自定义容器元素(无提升)

multithreading - 为什么 Rust 互斥锁似乎没有给最后想要锁定它的线程锁?

c - OpenMP 中的插入排序

c++ - 当怀疑 n log n 时,运行时复杂度呈线性