无法仅使用 OpenMP 进行矢量化
Unable to vectorize using only OpenMP
我正在尝试了解有关如何矢量化我的代码以提高性能的一些基础知识。
问题: 对于 -O0
我尝试使用 OpenMP SIMD 指令如下:
struct aligned_free
{
inline void operator()(double* ptr)
{
if (ptr != nullptr)
{
std::free(ptr);
}
}
};
using unique_ptr_aligned_double = std::unique_ptr<double, aligned_free>;
auto result = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_a = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_b = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_c = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_d = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
auto* r = result.get();
const auto* a = list_a.get();
const auto* b = list_b.get();
const auto* c = list_c.get();
const auto* d = list_d.get();
auto k_index = std::size_t{};
#pragma omp simd safelen(4) linear(k_index:1)
for (; k_index < n; ++k_index)
{
r[k_index] = a[k_index] * b[k_index];
}
令我失望的是,上面的代码没有向量化。我在这里错过了什么?我应该手动展开循环还是以任何其他方式帮助编译器?或者我应该开始使用编译器内部函数而不是 OpenMP?
旁白问题: 在执行此操作时,我问自己一个问题:使用编译器标志 -O2/O3
还是手动选择更好?我希望进行的优化,例如 -xHost
、-faggressive-loop-optimizations
等?在我(不知情的)自己看来,对我的编译器所做的事情有更准确的了解可能是一个更好的主意。
在 GCC、ICC 和 Clang 上,omp simd
影响 自动矢量化 优化步骤(通过向循环提供元信息)。但是,该步骤仅在启用优化 时才启用。因此,对于三个编译器,使用 -O0
的 pragma 注释将被简单地忽略。这是预期的行为。 Here 是三个编译器的结果。
有些编译器在 -O2
(ICC) 中启用了自动矢量化,而有些编译器在 -O3
(GCC 和可能的 Clang)中启用了自动矢量化。因为 -On
(n
是一个整数)只是一组明确定义的优化(从一个编译器到另一个编译器)。您可以指定向量化循环所需的优化标志(例如 -ftree-vectorize
用于 GCC)。虽然如果您使用一种特定的编译器(更确定性和更细粒度的控制),这往往会更好,但这对于可移植性来说并不是很好(所有编译器的选项都不相同,并且可能会在版本之间发生变化)。
此外,请注意,您不应忘记对 GCC/Clang 使用 -fopenmp-simd
,对 ICC 使用 -qopenmp-simd
。这对 Clang 来说尤为重要。另请注意,循环中需要 k_index = 0
。
最后,默认情况下,编译器倾向于不在 x86/x86-64 平台上使用 AVX、AVX2 和 AVX-512 指令,因为它并非在所有处理器上都可用(而是使用旧的 SSE 指令)。使用例如 -mavx
/-mavx2
启用 GCC/Clang 以生成更宽的 SIMD 指令(通常更快)。如果您打算既不分发生成的二进制文件也不打算在另一台机器上执行它们,则使用 -march=native
更好(否则,如果指令在目标机器上不受支持,生成的二进制文件可能会崩溃)。或者,您可以指定特定的体系结构,例如 -march=skylake
。 ICC 有类似的 options/flags.
有了所有这些,Clang、GCC 和 ICC 就能够生成正确的 SIMD 实现(生成的代码参见 here)。
我正在尝试了解有关如何矢量化我的代码以提高性能的一些基础知识。
问题: 对于 -O0
我尝试使用 OpenMP SIMD 指令如下:
struct aligned_free
{
inline void operator()(double* ptr)
{
if (ptr != nullptr)
{
std::free(ptr);
}
}
};
using unique_ptr_aligned_double = std::unique_ptr<double, aligned_free>;
auto result = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_a = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_b = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_c = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
const auto list_d = unique_ptr_aligned_double(static_cast<double*>(std::aligned_alloc(64, n * sizeof(double))), aligned_free());
auto* r = result.get();
const auto* a = list_a.get();
const auto* b = list_b.get();
const auto* c = list_c.get();
const auto* d = list_d.get();
auto k_index = std::size_t{};
#pragma omp simd safelen(4) linear(k_index:1)
for (; k_index < n; ++k_index)
{
r[k_index] = a[k_index] * b[k_index];
}
令我失望的是,上面的代码没有向量化。我在这里错过了什么?我应该手动展开循环还是以任何其他方式帮助编译器?或者我应该开始使用编译器内部函数而不是 OpenMP?
旁白问题: 在执行此操作时,我问自己一个问题:使用编译器标志 -O2/O3
还是手动选择更好?我希望进行的优化,例如 -xHost
、-faggressive-loop-optimizations
等?在我(不知情的)自己看来,对我的编译器所做的事情有更准确的了解可能是一个更好的主意。
在 GCC、ICC 和 Clang 上,omp simd
影响 自动矢量化 优化步骤(通过向循环提供元信息)。但是,该步骤仅在启用优化 时才启用。因此,对于三个编译器,使用 -O0
的 pragma 注释将被简单地忽略。这是预期的行为。 Here 是三个编译器的结果。
有些编译器在 -O2
(ICC) 中启用了自动矢量化,而有些编译器在 -O3
(GCC 和可能的 Clang)中启用了自动矢量化。因为 -On
(n
是一个整数)只是一组明确定义的优化(从一个编译器到另一个编译器)。您可以指定向量化循环所需的优化标志(例如 -ftree-vectorize
用于 GCC)。虽然如果您使用一种特定的编译器(更确定性和更细粒度的控制),这往往会更好,但这对于可移植性来说并不是很好(所有编译器的选项都不相同,并且可能会在版本之间发生变化)。
此外,请注意,您不应忘记对 GCC/Clang 使用 -fopenmp-simd
,对 ICC 使用 -qopenmp-simd
。这对 Clang 来说尤为重要。另请注意,循环中需要 k_index = 0
。
最后,默认情况下,编译器倾向于不在 x86/x86-64 平台上使用 AVX、AVX2 和 AVX-512 指令,因为它并非在所有处理器上都可用(而是使用旧的 SSE 指令)。使用例如 -mavx
/-mavx2
启用 GCC/Clang 以生成更宽的 SIMD 指令(通常更快)。如果您打算既不分发生成的二进制文件也不打算在另一台机器上执行它们,则使用 -march=native
更好(否则,如果指令在目标机器上不受支持,生成的二进制文件可能会崩溃)。或者,您可以指定特定的体系结构,例如 -march=skylake
。 ICC 有类似的 options/flags.
有了所有这些,Clang、GCC 和 ICC 就能够生成正确的 SIMD 实现(生成的代码参见 here)。