如何并行化循环

How to parallelize a loop?

本文关键字:循环 并行化      更新时间:2023-10-16

我在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");
}

你有什么建议我该怎么做吗?谢谢!

串行和并行运行将给出不同的结果,因为在

#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]. 因此,您更改了其他线程使用的数据。 这将导致读写数据竞赛!

这个

#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 个线程,每个"颜色"一个,而不会让线程竞相访问相同的数据。 但是,缓存中空间局部性的中断可能会对速度产生不良影响。

如果我遇到任何其他选项,我会编辑它们。

在问题中并行化循环嵌套很棘手,但可行。 Lamport的论文"DO循环的并行执行"涵盖了该技术。 基本上,您必须将 (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
}

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

如果你问一般的并行性,那么另一个可能的答案是矢量化。你可以实现一些相对较差的矢量并行izm(大约2倍加速),没有 更改数据结构和代码库。这可以使用 OpenMP4.0 或 CilkPlus 编译指示 simd 或类似版本(使用 safelen/vectorlength(2))

好吧,你确实有数据依赖性(内部和外部循环),但它属于"WAR"[(读后写)依赖关系子类别,它是使用"omp 并行"按原样"的障碍,但不一定是"编译指示 omp simd"循环的问题。

要使其正常工作,您需要通过OpenMP4或CilkPlus(最近的gcc或Intel编译器)支持编译指示的x86编译器。