7

我们正在运行一个科学程序,我们希望实现 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寄存器列表。

4

2 回答 2

7

正如您推测的那样,问题在于您没有告诉 GCC 您正在破坏哪些寄存器。如果他们还不支持将 YMM 寄存器放入 clobber 列表中,我会感到惊讶;你使用的是什么版本的 GCC?

无论如何,将相应的 XMM 寄存器放在 clobber 列表中几乎肯定就足够了:

: "=m" (ret) : "m" (*a), "m" (*b) : "%xmm1", "%xmm2");

其他一些注意事项:

  • 您将两个输入加载两次,这是低效的。没有理由这样做。
  • 我将"r" (a), "r" (b)用作约束并编写我的负载,例如vmovupd (%2), %%ymm1. 生成的代码可能没有区别,但它似乎更符合习惯用法。
  • 不要忘记vzeroupper在执行任何 SSE 代码之前放置以下 AVX 代码以避免(大)停顿。
于 2013-04-02T14:44:16.967 回答
3

我添加两条评论,而不是直接回答您的问题:

  • 我强烈建议使用编译器内在函数而不是直接汇编。这样,编译器负责寄存器分配,并且可以更好地优化您的代码(内联方法、重新排序指令等)
  • Agner Fog有一个优化的向量化操作的C++ 向量类库,包括对复数的操作。即使您可能无法在 C 代码中直接使用他的库,他的优化代码也可能是一个很好的起点;见压缩源代码src/special/complexvec.h
于 2013-04-03T15:40:06.753 回答