如何并行化循环?

时间:2013-09-05 10:25:45

标签: c++ parallel-processing openmp

我在C ++上使用OpenMP,我希望并行化非常简单的循环。但我不能正确地做到这一点。我一直得错了结果。

for(i=2;i<N;i++)
    for(j=2;j<N;j++)
         A[i,j] =A[i-2,j] +A[i,j-2];

代码:

int const N = 10;
int arr[N][N];

#pragma omp parallel for
for (int i = 0; i < N; i++)
    for (int j = 0; j < N; j++)
        arr[i][j] = 1;

#pragma omp parallel for 
for (int i = 2; i < N; i++)
    for (int j = 2; j < N; j++)
    {
        arr[i][j] = arr[i-2][j] +arr[i][j-2];
    }

for (int i = 0; i < N; i++)
{
    for (int j = 0; j < N; j++)
        printf_s("%d     ",arr[i][j]);
    printf("\n");
}

你有什么建议我怎么办?谢谢!

4 个答案:

答案 0 :(得分:3)

串行和并行运行会给出不同的。结果因为在

#pragma omp parallel for 
for (int i = 2; i < N; i++)
    for (int j = 2; j < N; j++)
    {
        arr[i][j] = arr[i-2][j] +arr[i][j-2];
    }
    .....
你正在更新arr [i]。所以你改变了另一个线程使用的数据。它将导致读写数据竞争!

答案 1 :(得分:3)

这个

#pragma omp parallel for 
for (int i = 2; i < N; i++)
    for (int j = 2; j < N; j++)
    {
        arr[i][j] = arr[i-2][j] +arr[i][j-2];
    }

总是会成为悲伤和不可预测的输出的源泉。 OpenMP运行时将为每个线程提供i的一系列值,并将它们保留给它。线程更新arr的相对顺序没有确定性。例如,当线程1正在使用i = 2,3,4,5,...,100(或其他)更新元素,而线程2正在使用i = 102,103,104,...,200更新元素时​​,程序不会确定线程1是否在线程2之前或之后更新arr[i,:] = 100想要使用arr中的更新值。您编写了一个包含经典数据竞赛的代码。

您可以通过多种方式解决此问题:

您可以尝试确保线程以正确(即顺序)顺序更新arr。最终结果将是一个OpenMP程序,其运行速度比顺序程序慢。不要选择这个选项。

您可以制作2份arr副本,并始终从一个更新到另一个,然后从另一个更新为一个。像(非常伪代码)

的东西
for ...
{
    old = 0
    new = 1

    arr[i][j][new] = arr[i-2][j][old] +arr[i][j-2][old];

    old = 1
    new = 0
}

当然,第二种方法是时间换空间,但这通常是一种合理的权衡。

您可能会发现向arr添加额外的平面并不能立即加快速度,因为它会破坏拉入缓存的值的空间局部性。尝试一下,可能使[old]成为第一个索引元素而不是最后一个。

由于更新数组中的每个元素取决于在2行/列之外的元素中找到的值,因此您可以将数组像棋盘一样有效地分割成白色和黑色元素。你可以使用2个线程,每个'颜色'一个,没有线程竞争访问相同的数据。但同样,缓存中空间局部性的中断可能会对速度产生不良影响。

如果我发现任何其他选项,我会在其中进行编辑。

答案 2 :(得分:3)

要在问题中并行化循环嵌套是棘手的,但可行。 Lamport的论文"The Parallel Execution of DO Loops"涵盖了该技术。基本上你必须将你的(i,j)坐标旋转45度到一个新的坐标系(k,l),其中k = i + j和l = i-j。

虽然要实际获得加速,但迭代可能必须分组到切片中,这使代码更加丑陋(四个嵌套循环)。

完全不同的方法是使用OpenMP任务递归地解决问题。递归是:

if( too small to be worth parallelizing ) {
    do serially
} else {
    // Recursively:
    Do upper left quadrant
    Do lower left and upper right quadrants in parallel
    Do lower right quadrant
}

实际上,算术运算与内存访问的比例非常低,以至于很难从示例中获得加速。

答案 3 :(得分:0)

如果你一般都会问平行性,那么另一个可能的答案就是矢量化。没有,你可以实现一些相对较差的矢量parallelizm(类似于2x加速等)  改变数据结构和代码库。这可以使用OpenMP4.0或CilkPlus pragma simd或类似(使用safelen / vectorlength(2))

嗯,你真的有数据依赖(内部循环和外部循环),但它属于«WAR»[(写入后读取)依赖项子类别,这是阻止使用«omp parallel for««按原样»但不一定是«pragma omp simd»循环的问题。

要使其正常工作,您需要通过OpenMP4或CilkPlus(非常新的gcc或Intel编译器)支持pragma simd的x86编译器。