c - 使用 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
}
解决方案
基于-j
的循环是一个并行循环,因此您不能j
在循环之后使用。尤其如此,因为您明确放置j
as 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
。
推荐阅读
- jpa - 如何在运行时根据条件使用不同的 EntityManager?
- php - 通过 Windows 的 bash 终端安装的 PHP 比 Windows 10 的 php 执行得更快
- c# - Web 服务 - Azure 云服务中未找到文件异常
- swift - 使用@IBInspectable Swift 4.2 的 UIView 上的 CornerRadius 和阴影
- json - Viber REST API[GATEWAY_TIMEOUT]
- java - 如何在 Spring Boot wicket 应用程序中注入 RequestScope bean
- android - Android Studio 模板包名
- azure - Azure Functions 中的 Chrome 运行失败:尝试以访问权限禁止的方式访问套接字
- visual-studio - 解决方案文件中的差异
- delphi - Delphi,记录类型属性,记录字段分配:预期分配到记录的本地副本