问题描述
我正在尝试并行化具有循环范围之外的数据依赖性(最小)的程序的内部循环。我有一个问题,残差计算发生在内部 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
}
解决方法
基于 j
的循环是一个并行循环,所以不能在循环后使用 j
。尤其如此,因为您明确地将 j
设为 private
,因此仅在线程中局部可见,而在并行区域之外不可见。您可以在并行循环之后使用 j
显式计算剩余 (k-4+3)/4*4
值的位置。
此外,这里有几个要点:
- 您可能不需要自己矢量化代码:您可以使用
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 架构上正确矢量化。