使用 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 架构上正确矢量化。
我正在尝试并行化一个程序的内部循环,该程序具有循环范围之外的数据依赖性(最小值)。我遇到了一个问题,即剩余计算发生在内部 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 架构上正确矢量化。