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

C 利用SSE矢量化在OpenMP中利用残差计算并行化内环,c,openmp,sse,pragma,C,Openmp,Sse,Pragma,我正在尝试并行化一个程序的内部循环,该程序的数据依赖项(min)超出了循环的范围。我遇到了一个问题,剩余计算发生在内部j循环的范围之外。如果j循环中包含“#pragma omp parallel”部分,即使由于k值太低,循环根本没有运行,代码也会出错。比如说(1,2,3) for (i = 0; i < 10; i++) { #pragma omp parallel for shared(min) private (j, a, b, storer, arr) // fo

我正在尝试并行化一个程序的内部循环,该程序的数据依赖项(min)超出了循环的范围。我遇到了一个问题,剩余计算发生在内部j循环的范围之外。如果j循环中包含“#pragma omp parallel”部分,即使由于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
  }
(i=0;i<10;i++)的

{
#pragma omp并行用于共享(最小)私有(j、a、b、storer、arr)//
对于(j=0;j
基于
j
的循环是一个并行循环,因此不能在循环后使用
j
。这一点尤其正确,因为您将
j
显式地设置为
private
,因此只能在线程中局部可见,而不能在并行区域之外。您可以在并行循环之后使用
(k-4+3)/4*4
显式计算剩余
j
值的位置

此外,这里有几个要点:

  • 您可能真的不需要自己对代码进行矢量化:您可以使用
    omp simd reduce
    。OpenMP可以为您自动完成所有枯燥的剩余计算工作。此外,代码将是可移植的,并且更加简单。生成的代码也可能比您的代码快。但是请注意,有些编译器可能无法对代码进行矢量化(GCC和ICC可以,而Clang和MSVC通常需要一些帮助)
  • 关键部分(
    omp Critical
    )的成本非常高。在您的情况下,这将消除与并行部分相关的任何可能的改进。由于缓存线反弹,代码可能会变慢
  • 尽管某些编译器(如GCC)可能能够理解代码的逻辑并生成更快的实现(提取车道数据),但在这里读取由
    \u mm\u store\u ps
    编写的数据效率很低
  • 水平SIMD减少效率低下。使用垂直的,速度快得多,可以很容易地在这里使用
以下是考虑到以上几点的修正代码:

(i=0;i<10;i++)的

{
//假设min已在此处正确初始化
#pragma omp并行用于simd简化(最小:最小)专用(j)
对于(j=0;j

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

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

此外,这里有几个要点:

  • 您可能真的不需要自己对代码进行矢量化:您可以使用
    omp simd reduce
    。OpenMP可以为您自动完成所有枯燥的剩余计算工作。此外,代码将是可移植的,并且更加简单。生成的代码也可能比您的代码快。但是请注意,有些编译器可能无法对代码进行矢量化(GCC和ICC可以,而Clang和MSVC通常需要一些帮助)
  • 关键部分(
    omp Critical
    )的成本非常高。在您的情况下,这将消除与并行部分相关的任何可能的改进。由于缓存线反弹,代码可能会变慢
  • 尽管某些编译器(如GCC)可能能够理解代码的逻辑并生成更快的实现(提取车道数据),但在这里读取由
    \u mm\u store\u ps
    编写的数据效率很低
  • 水平SIMD减少效率低下。使用垂直的,速度快得多,可以很容易地在这里使用
以下是考虑到以上几点的修正代码:

(i=0;i<10;i++)的

{
//假设min已在此处正确初始化
#pragma omp并行用于simd简化(最小:最小)专用(j)
对于(j=0;j

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

您能更具体地说明您遇到了什么错误吗?这些是编译时错误还是得到了错误的结果?我的第一个想法是,在
min
变量上有一个竞争条件,因此显而易见的解决方案是
reduce(min:min)
子句,您必须将其添加到
parallel for
指令中。您只需使用
\u mm\u min\u ps
获得4个最小值的向量,并将其减少到末尾的一个元素(a