使用 SSE 矢量化在 OpenMP 中使用残差计算并行化内循环

Parallelizing inner loop with residual calculations in OpenMP with SSE vectorization

我正在尝试并行化一个程序的内部循环,该程序具有循环范围之外的数据依赖性(最小值)。我遇到了一个问题,即剩余计算发生在内部 j 循环的范围之外。如果“#pragma omp parallel”部分包含在 j 循环中,即使由于 k 值太低,循环根本没有 运行,代码也会出错。例如说 (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
  }

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

此外,这里有几个要点:

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

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