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

时间:2019-07-17 08:00:42

标签: 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指令的位置,它将构造并行区域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编写的多线程程序的流程。

1 个答案:

答案 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}}

我希望这从逻辑的角度阐明那里发生的事情。