首页 > 解决方案 > 使用 SSE 向量化在 OpenMP 中将内部循环与残差计算并行化

问题描述

我正在尝试并行化具有循环范围之外的数据依赖项(最小值)的程序的内部循环。我遇到了一个问题,即残差计算发生在内 j 循环范围之外。如果 j 循环中包含“#pragma omp parallel”部分,即使由于 ak 值太低而导致循环根本没有运行,代码也会出错。比如说 (1,2,3)。

for (i = 0; i < 10; i++)
  {
    #pragma omp parallel for shared(min) private (j, a, b, storer, arr) //
    for (j = 0; j < k-4; j += 4)
    {
      mm_a = _mm_load_ps(&x[j]);
      mm_b = _mm_load_ps(&y[j]);
      mm_a = _mm_add_ps(mm_a, mm_b);
      _mm_store_ps(storer, mm_a);

      #pragma omp critical
      {
      if (storer[0] < min)
      {
        min = storer[0];
      }
      if (storer[1] < min)
      {
        min = storer[1];
      }
      //etc
      }
    }
    do
    {
        #pragma omp critical
        {
        if (x[j]+y[j] < min)
        {
          min = x[j]+y[j];
        }    
        } 
      }
    } while (j++ < (k - 1));
    round_min = min
  }

标签: copenmpssepragma

解决方案


基于-j的循环是一个并行循环,因此您不能j在循环之后使用。尤其如此,因为您明确放置jas private,因此仅在线程中本地可见,但在并行区域之外不可见。您可以在并行循环之后使用显式计算剩余j值的位置。(k-4+3)/4*4

此外,这里有几个要点:

  • 您可能真的不需要自己对代码进行矢量化:您可以使用omp simd reduction. OpenMP 可以自动为您完成计算残差计算的所有枯燥工作。此外,代码将是可移植的并且更简单。生成的代码也可能比你的更快。但是请注意,某些编译器可能无法对代码进行矢量化(GCC 和 ICC 可以,而 Clang 和 MSVC 通常需要一些帮助)。
  • 临界区( omp critical) 非常昂贵。在您的情况下,这只会消除与并行部分相关的任何可能的改进。由于缓存行弹跳,代码可能会变慢
  • 尽管某些编译器(如 GCC)可能能够理解您的代码逻辑并生成更快的实现(提取通道数据),但在这里读取 by 写入的数据_mm_store_ps效率很低。
  • 水平 SIMD 减少效率低下。使用速度更快并且可以在这里轻松使用的垂直的。

考虑到上述几点,这是一个更正的代码:

for (i = 0; i < 10; i++)
{
    // Assume min is already initialized correctly here

    #pragma omp parallel for simd reduction(min:min) private(j)
    for (j = 0; j < k; ++j)
    {
        const float tmp = x[j] + y[j];
        if(tmp < min)
            min = tmp;
    }

    // Use min here
}

-O3 -fopenmp上面的代码在 GCC/ICC(带有)、Clang(带有-O3 -fopenmp -ffastmath)和 MSVC(带有)上的 x86 架构上正确矢量化/O2 /fp:precise -openmp:experimental


推荐阅读