用于复数乘法的汇编代码/AVX 指令。 (GCC 内联汇编)
Posted
技术标签:
【中文标题】用于复数乘法的汇编代码/AVX 指令。 (GCC 内联汇编)【英文标题】:Assembly code/AVX instructions for multiplication of complex numbers. (GCC inline assembly) 【发布时间】:2013-04-02 14:35:36 【问题描述】:我们正在运行一项科学计划,并且希望实现 AVX 功能。整个程序(用 Fortran+C 编写)将被向量化,目前我正在尝试在 GCC 内联汇编中实现复数乘法。
汇编代码接受 4 个复数并一次执行两个复数乘法:
v2complex cmult(v2complex *a, v2complex *b)
v2complex ret;
asm (
"vmovupd %2,%%ymm1;"
"vmovupd %2, %%ymm2;"
"vmovddup %%ymm2, %%ymm2;"
"vshufpd $15,%%ymm1,%%ymm1,%%ymm1;"
"vmulpd %1, %%ymm2, %%ymm2;"
"vmulpd %1, %%ymm1, %%ymm1;"
"vshufpd $5,%%ymm1,%%ymm1, %%ymm1;"
"vaddsubpd %%ymm1, %%ymm2,%%ymm1;"
"vmovupd %%ymm1, %0;"
:
"=m"(ret)
:
"m" (*a),
"m" (*b)
);
return ret;
其中 a 和 b 是 256 位双精度:
typedef union v2complex
__m256d v;
complex c[2];
v2complex;
问题是代码大多会产生正确的结果,但有时会失败。
我对组装很陌生,但我试图自己弄清楚。似乎 C 程序(优化的 -O3)与汇编代码中使用的寄存器 ymm
交互。例如,我可以在执行乘法之前打印其中一个值(例如 a),并且程序永远不会给出错误的结果。
我的问题是如何告诉 GCC 不要与 ymm 交互。我没能做到
将ymm
放到被破坏的寄存器列表中。
【问题讨论】:
【参考方案1】:正如您推测的那样,问题在于您没有告诉 GCC 您正在破坏哪些寄存器。如果他们还不支持将 YMM 寄存器放入 clobber 列表中,我会感到惊讶;你使用的是什么版本的 GCC?
无论如何,将相应的 XMM 寄存器放在 clobber 列表中几乎肯定就足够了:
: "=m" (ret) : "m" (*a), "m" (*b) : "%xmm1", "%xmm2");
其他一些注意事项:
您将两个输入加载两次,效率低下。没有理由这样做。 我会使用"r" (a), "r" (b)
作为约束,并像vmovupd (%2), %%ymm1
这样写我的负载。生成的代码可能没有区别,但它似乎更符合习惯用法。
在执行任何 SSE 代码之前,不要忘记在 AVX 代码后面加上 vzeroupper
,以避免(大)停顿。
【讨论】:
非常感谢,解决了问题=)。我正在使用 gcc 4.7.2 和 thx 为您提供建议。 不要将“r” (a), “r” (b)
与vmovupd (%2), %%ymm1
等一起使用,GCC 将假定 *a 和 *b 未被访问(除非您添加“内存”破坏者)。【参考方案2】:
我加了两个cmets,不直接回答你的问题:
我强烈建议使用编译器内在函数而不是直接汇编。这样,编译器会负责寄存器分配,并且可以更好地优化代码(内联方法、重新排序指令等) Agner Fog 有一个 C++ vector class library 优化矢量化操作,包括对复数的操作。即使您可能无法在 C 代码中直接使用他的库,他的优化代码也可能是一个很好的起点;请参阅the zipped source code 中的src/special/complexvec.h
。
【讨论】:
谢谢,我会看看它,即使我将无法使用它。实际上,我已经编译了两个版本,内部代码和程序集,我想知道为什么程序集运行不好,因为它们都编译为相同的优化程序集代码(objdump -S ..)。 但是内在函数也有缺点:编译器负责寄存器分配和重新排序指令等。通常编译器不会更好地做到这一点。以上是关于用于复数乘法的汇编代码/AVX 指令。 (GCC 内联汇编)的主要内容,如果未能解决你的问题,请参考以下文章
使用 openmp 并行化矩阵乘法并使用 avx2 进行矢量化
在 GCC 上设置打包的 long long 的正确对齐以与 avx2 指令一起使用