c++ - 为什么对齐限制会在矢量化时改变 clang 的行为?
问题描述
谁能解释一下clang的行为?
Array4Complex64 f1(Array4Complex64 a, Array4Complex64 b){
return a * b;
}
此函数计算 a 和 b 中的每个元素的复数乘积。我编译了两次,一次对 Array4Complex64 类型有对齐限制,一次没有。结果如下:
对齐:
f1(Array4Complex64, Array4Complex64): # @f1(Array4Complex64, Array4Complex64)
push rbp
mov rbp, rsp
and rsp, -32
sub rsp, 32
mov rax, rdi
vmovapd ymm0, ymmword ptr [rbp + 80]
vmovapd ymm1, ymmword ptr [rbp + 112]
vmovapd ymm2, ymmword ptr [rbp + 16]
vmovapd ymm3, ymmword ptr [rbp + 48]
vmulpd ymm4, ymm1, ymm3
vfmsub231pd ymm4, ymm0, ymm2 # ymm4 = (ymm0 * ymm2) - ymm4
vmovapd ymmword ptr [rdi], ymm4
vmulpd ymm0, ymm3, ymm0
vfmadd231pd ymm0, ymm1, ymm2 # ymm0 = (ymm1 * ymm2) + ymm0
vmovapd ymmword ptr [rdi + 32], ymm0
mov rsp, rbp
pop rbp
vzeroupper
ret
没有:
f1(Array4Complex64, Array4Complex64): # @f1(Array4Complex64, Array4Complex64)
mov rax, rdi
vmovupd ymm0, ymmword ptr [rsp + 72]
vmovupd ymm1, ymmword ptr [rsp + 104]
vmovupd ymm2, ymmword ptr [rsp + 8]
vmovupd ymm3, ymmword ptr [rsp + 40]
vmulpd ymm4, ymm1, ymm3
vfmsub231pd ymm4, ymm0, ymm2 # ymm4 = (ymm0 * ymm2) - ymm4
vmovupd ymmword ptr [rdi], ymm4
vmulpd ymm0, ymm3, ymm0
vfmadd231pd ymm0, ymm1, ymm2 # ymm0 = (ymm1 * ymm2) + ymm0
vmovupd ymmword ptr [rdi + 32], ymm0
vzeroupper
ret
结果是一样的,但是地址的计算方式不同:一次是相对于 rbp,一次是相对于 rsp。它不限于乘法,适用于任何计算。一个版本比另一个更好吗?
解决方案
第一种方式是无用的对齐RSP(并在过程中将RBP设置为帧指针,因此自然而然地使用它)。这显然是一个错过的优化,因为它实际上并没有将这些函数参数中的任何一个溢出到堆栈中。(因为这不是调试版本)。您可以将此报告给http://bugs.llvm.org/。包含一个 MCVE 的源代码,以及这个 asm 输出。
不幸的是,这两种方式都是在堆栈内存中传递值,而不是 YMM 寄存器。:( x86-64 System V 可以在 YMM 寄存器中传递一个结构,如果它都是 FP 并且大小为 32 字节。(您可能必须手动将 64 字节参数分解为两个单独的 32 字节参数才能有效传递,如果你不能让它内联,你不能使用 AVX-512 让它在单个 ZMM 寄存器中传递。)
调用者负责在栈上按值传递对齐对象时对齐RSP;被调用者对 RSP 所做的事情不会影响数据所在的地址,也不会复制它。
很明显,这会降低这个特定函数的效率,但是这个函数太小了,你绝对应该确保它内联到任何调用站点,此时所有这些开销都消失了。 (或者至少对较大的函数进行一次不必要的 RSP 对齐,而不是每次调用小函数一次。)
调用者必须运行至少 4vmovapd
条存储指令,并且比调用站点周围的更多,特别是因为 x86-64 System V 没有保留调用的 ymm(甚至 xmm)寄存器。因此,任何其他存在于寄存器中的 FP / 向量变量或临时变量都必须在调用此函数时溢出。并且调用开销本身需要前端一些时间和一些静态代码大小。
非内联版本的每个调用站点的代码量可能与仅内联它所需的代码量相似!这意味着内联是纯粹的胜利。
推荐阅读
- python - 如何在 Tkinter 中将类/窗口从“普通”窗口转换为 Toplevel() 窗口?
- python - Python 缓存工具在多核服务器中共享内存
- regex - 从文件中提取特定列?
- java - 使用spring boot在linkedin中实现自动发布
- javascript - 如何使用 API 生成的 URL 在 Vue 中设置背景图像的值
- performance - 提高 RestApi 命中共享点文件夹的性能
- javascript - 原型链如何在 Javascript 中工作?
- java - actionListener 内部的 JTextField.getText() 无法返回正确的字符串
- javascript - 在类主体中定义函数而不是在构造函数中
- node.js - 使用 Node.js 中的 findByIdAndUpdate 方法更新 mongoDB 中的文档,其中用户模型具有唯一键“电子邮件”