gpt4 book ai didi

c - 通过 OpenMP SIMD 进行的 256 位矢量化会阻止编译器的优化(比如函数内联)?

转载 作者:太空狗 更新时间:2023-10-29 17:24:05 26 4
gpt4 key购买 nike

考虑以下玩具示例,其中 A 是以列优先顺序存储的 n x 2 矩阵,我想计算它的列总和。 sum_0 只计算第一列的和,而 sum_1 也计算第二列。这实际上是一个人为的例子,因为基本上不需要为此任务定义两个函数(我可以编写一个带有双循环嵌套的函数,其中外部循环从 0 迭代到 j).它是为了演示我在现实中遇到的模板问题而构建的。

/* "test.c" */
#include <stdlib.h>

// j can be 0 or 1
static inline void sum_template (size_t j, size_t n, double *A, double *c) {

if (n == 0) return;
size_t i;
double *a = A, *b = A + n;
double c0 = 0.0, c1 = 0.0;

#pragma omp simd reduction (+: c0, c1) aligned (a, b: 32)
for (i = 0; i < n; i++) {
c0 += a[i];
if (j > 0) c1 += b[i];
}

c[0] = c0;
if (j > 0) c[1] = c1;

}

#define macro_define_sum(FUN, j) \
void FUN (size_t n, double *A, double *c) { \
sum_template(j, n, A, c); \
}

macro_define_sum(sum_0, 0)
macro_define_sum(sum_1, 1)

如果我编译它

gcc -O2 -mavx test.c

GCC(例如最新的 8.2)在内联、常量传播和死代码消除之后,将针对函数 sum_0 ( Check it on Godbolt ) 优化涉及 c1 的代码。

我喜欢这个技巧。通过编写单个模板函数并传入不同的配置参数,优化编译器可以生成不同的版本。它比复制和粘贴大部分代码并手动定义不同的函数版本要干净得多。

但是,如果我用 激活 OpenMP 4.0+ 就失去了这种便利

gcc -O2 -mavx -fopenmp test.c

sum_template 不再内联,也不再应用无用代码消除 ( Check it on Godbolt )。但是,如果我删除标志 -mavx 以使用 128 位 SIMD,编译器优化将按我预期的方式工作 (Check it on Godbolt)。那么这是一个错误吗?我在 x86-64 (Sandybridge) 上。


备注

使用 GCC 的自动矢量化 -ftree-vectorize -ffast-math 不会有这个问题 ( Check it on Godbolt )。但我希望使用 OpenMP,因为它允许跨不同编译器的可移植对齐编译指示。

背景

我为 R 包编写模块,需要跨平台和编译器移植。编写 R 扩展不需要 Makefile。当 R 在平台上构建时,它知道该平台上的默认编译器是什么,并配置一组默认编译标志。 R 没有自动矢量化标志,但它有 OpenMP 标志。这意味着使用 OpenMP SIMD 是在 R 包中使用 SIMD 的理想方式。参见 12详细说明。

最佳答案

解决此问题的最简单方法是使用 __attribute__((always_inline)) 或其他特定于编译器的覆盖。

#ifdef __GNUC__
#define ALWAYS_INLINE __attribute__((always_inline)) inline
#elif defined(_MSC_VER)
#define ALWAYS_INLINE __forceinline inline
#else
#define ALWAYS_INLINE inline // cross your fingers
#endif


ALWAYS_INLINE
static inline void sum_template (size_t j, size_t n, double *A, double *c) {
...
}

Godbolt proof that it works.

此外,不要忘记使用 -mtune=haswell,而不仅仅是 -mavx。这通常是个好主意。 (但是,有前途的对齐数据将阻止 gcc 的默认 -mavx256-split-unaligned-load 调整将 256 位负载拆分为 128 位 vmovupd + vinsertf128,所以 this 函数的代码生成与 tune=haswell 没问题。但通常你希望 gcc 的这个能够自动向量化任何其他函数。

你真的不需要 staticinline;如果编译器决定不内联它,它至少可以在编译单元之间共享相同的定义。


通常 gcc 根据函数大小试探法决定内联或不内联。但即使设置 -finline-limit=90000 也不会让 gcc 与您的 #pragma omp ( How do I force gcc to inline a function? ) 内联。我一直在猜测 gcc 没有意识到内联后的持续传播会简化条件,但 90000 个“伪指令”似乎很大。可能还有其他启发式方法。

可能 OpenMP 以不同的方式设置了一些每个函数的东西,如果它让它们内联到其他函数,可能会破坏优化器。使用 __attribute__((target("avx"))) 阻止该函数内联到未使用 AVX 编译的函数中(因此您可以安全地进行运行时调度,而无需跨 <if(avx) 条件。)

OpenMP 做的一件事是常规自动矢量化无法做到的,即可以在不启用 -ffast-math 的情况下对缩减进行矢量化。

不幸的是,OpenMP 仍然懒得展开多个累加器或任何隐藏 FP 延迟的东西。 #pragma omp 是一个很好的提示,表明循环实际上很热并且值得花费代码大小,因此 gcc 应该真正做到这一点,即使没有 -fprofile-use

因此,特别是如果它在 L2 或 L1 缓存(或可能是 L3)中的热数据上运行,您应该采取一些措施来获得更好的吞吐量。

顺便说一句,对于 Haswell 上的 AVX,对齐通常不是什么大问题。但是对于 SKX 上的 AVX512,64 字节对齐在实践中确实更重要。例如,未对齐的数据可能会减速 20%,而不是几个 %。

(但在编译时 promise 对齐与实际在运行时对齐数据是不同的问题。两者都有帮助,但在编译时 promise 对齐会使 gcc7 和更早版本的代码更紧凑,或者在没有 AVX 的任何编译器上。)

关于c - 通过 OpenMP SIMD 进行的 256 位矢量化会阻止编译器的优化(比如函数内联)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51152215/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com