编译器是否实际使用我的"omp declare simd"函数?
Does the compiler actually use my "omp declare simd" functions?
看看我为4D点积构建的这个例子:
#pragma omp declare simd
double dot(double x0, double y0, double z0, double w0, double x1, double y1, double z1, double w1)
{
return x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1;
}
#define SIMD 4
int main(int argc, char **argv)
{
double x[SIMD];
double y[SIMD];
double z[SIMD];
double w[SIMD];
double r[SIMD];
for (int i = 0; i < SIMD; i++)
{
x[i] = y[i] = z[i] = 1;
w[i] = 0;
}
#pragma omp simd
for (int i = 0; i < SIMD; i++)
{
r[i] = dot(x[i], y[i], z[i], w[i], x[i], y[i], z[i], w[i]);
}
double s = 0;
for (int i = 0; i < SIMD; i++)
{
s += r[i];
}
return s;
}
在编译器输出中,您可以看到它生成了一些称为_XXXXXXvvvvvvvv_dot
的函数。我假设这些是用于dot
函数不同长度输入的函数,或者至少它们是应该的。但是,编译器似乎并未实际使用这些函数。输出的第 94 行读取call dot(…)
。这是否调用这些函数之一?我必须做什么才能使用它们?
不要尝试手动调用 SIMD 版本:让编译器从它自动矢量化的循环中执行此操作。
您没有启用优化,因此 GCC 不会自动矢量化您的循环。 因此,它只调用函数的标量版本。
GCC 默认值是-O0
- 针对调试进行反优化,因此代码当然完全是垃圾,实际上并没有自动矢量化(没有addpd
或mulpd
指令)。
使用-O3
启用优化。 GCC 在可以看到定义时将简单地内联调用。#pragma omp declare simd
的事情允许编译器发出对函数的矢量化版本的调用,即使它看不到定义。 (或者对于它选择不内联的较大函数。
您可以在dot
上使用__attribute__((noinline))
来查看它如何工作,甚至对于您的小函数也是如此:
在带有GCC9.1-O3 -fopenmp
的Godbolt上,进行了该更改:
# gcc9.1 -O3 -fopenmp
main:
sub rsp, 40
movapd xmm0, XMMWORD PTR .LC0[rip] # {1, 1}
pxor xmm7, xmm7 # {0, 0}
movapd xmm3, xmm7
movapd xmm6, xmm0 # duplicate the 1,1 vector for several args
movapd xmm5, xmm0
movapd xmm4, xmm0
movapd xmm2, xmm0
movapd xmm1, xmm0
call _ZGVbN2vvvvvvvv_dot(double, double, double, double, double, double, double, double)
movaps XMMWORD PTR [rsp], xmm0 # store to the stack
movaps XMMWORD PTR [rsp+16], xmm0 # twice
pxor xmm0, xmm0 # 0.0
addsd xmm0, QWORD PTR [rsp] # 0 + v[0]
addsd xmm0, QWORD PTR [rsp+8] # ... += v[1]
addsd xmm0, QWORD PTR [rsp+16]
addsd xmm0, QWORD PTR [rsp+24] # stupid inefficient horizontal sum
add rsp, 40
cvttsd2si eax, xmm0 # truncate to integer as main's return value
ret
使用你的小#define SIMD 4
,main
实际上根本不需要循环,只需两个 16 字节的向量就足够了。 具有编译时常量初始值设定项的数组得到优化;GCC 只是将常量具体化到寄存器中,0.0 为pxor
-归零,并从静态常量数据加载 + 复制1.0
.
所以无论如何,只有一个调用 SIMD 版本的dot()
,但仅此而已。 我认为GCC知道相同的调用将给出相同的结果,这就是为什么它调用一次但存储结果两次的原因。
IDK为什么GCC的OpenMP水平总和如此愚蠢。 显然,最好addpd xmm0,xmm0
而不是存储两次,并且洗牌可以避免存储/重新加载。 使用addsd
来做0.0 + x
也是没有意义的;只需使用您存储的寄存器的低元素即可。
dot()
的标量版本具有函数通常的C++名称重整。 其他版本具有特殊的名称重整约定,可能特定于GCC的OpenMP,IDK。
有趣的是,gcc制作了几个不同版本的dot
,包括使用YMM寄存器的AVX版本。 还有一些溢出到堆栈并在循环中使用标量数学;IDK为什么存在这些。
所以我想这意味着即使您在没有-march=skylake-avx512
的情况下编译此源文件,以这种方式编译的另一个循环仍然可以发出对_ZGVeN8vvvvvvvv_dot
的调用并获取 AVX512 定义:
_ZGVeN8vvvvvvvv_dot(double, double, double, double, double, double, double, double):
vmulpd zmm1, zmm1, zmm5
vfmadd132pd zmm0, zmm1, zmm4
vfmadd231pd zmm0, zmm2, zmm6
vfmadd231pd zmm0, zmm3, zmm7
奇怪的是,我没有看到在YMM regs上使用FMA的AVX + FMA定义,只有使用vmulpd/vaddpd的SSE2和AVX定义。
- C++omp没有显著改善
- 等待整个 omp 块完成,然后再调用第二个函数
- OpenMP #pragma omp for v/s #pragma omp parallel for 之间的区别?
- 自定义 OMP 缩减 在 std::map 上
- 函数中的"pragma omp parallel for"在另一个并行循环中调用函数时无效
- OMP for 循环,无需初始化
- 为什么在杂注 omp 关键之后多次调用 printf 会产生乱码输出?
- GSL+OMP:C++中的线程安全随机数生成器
- 编译器是否实际使用我的"omp declare simd"函数?
- #define 的"Declaration does not declare anything"错误
- 如何在循环内将大对象复制到omp任务中?
- OMP 不启动线程
- 标准上的 OMP 和并行操作::set<...>::迭代器
- VS2017 中嵌套循环的 OMP
- 为什么"#pragma omp 关键"部分不能在同一行上具有左大括号?
- #pragma OMP平行的末尾是否有隐式障碍
- 使用 #omp 时'operator -'不匹配
- ICC:包括OMP.H需要BytesWap.H
- 为什么 omp 并行部分中的线程不会在其部分上划分?
- 一般情况下,在 cv::Mat 或 cv::Mat 的向量上进行 omp 还原