【问题标题】:PCLMULQDQ instruction in C inline asmC 内联汇编中的 PCLMULQDQ 指令
【发布时间】:2014-01-08 00:34:22
【问题描述】:

我想在我的 C 代码中使用 Intel 的 PCLMULQDQ 指令和内联汇编来将两个多项式相乘,它们是 GF(2^n) 中的元素。编译器是 GCC 4.8.1。 多项式存储在 uint32_t 数组中(6 个字段大)。

我已经在网上查了如何正确使用 PCLMULQDQ 指令或 CLMUL 指令集,但没有找到任何好的文档。

我非常感谢 C 和 asm 中的一个简单示例,说明如何将两个简单多项式与指令相乘。有人知道怎么做吗?

除此之外是否还有任何先决条件(功能强大的处理器除外),例如包含的库、编译器选项等?

【问题讨论】:

标签: c assembly intel inline-assembly instructions


【解决方案1】:

我已经找到了解决方案。因此记录在案:

void f2m_intel_mult(
  uint32_t t, // length of arrays A and B
  uint32_t *A,
  uint32_t *B,
  uint32_t *C
)
{
    memset(C, 0, 2*t*sizeof(uint32_t));
    uint32_t offset = 0;
    union{ uint64_t val; struct{uint32_t low; uint32_t high;} halfs;} prod;

    uint32_t i;
    uint32_t j;
    for(i=0; i<t; i++){
        for(j=0; j<t; j++){

            prod.halfs.low = A[i];
            prod.halfs.high = 0;
            asm ("pclmulqdq %2, %1, %0;"
            : "+x"(prod.val)
            : "x"(B[j]), "i"(offset)
            );

            C[i+j] = C[i+j] ^ prod.halfs.low;
            C[i+j+1] = C[i+j+1] ^ prod.halfs.high;
        }
    }
}

我认为可以为 pclmulqdq 使用 64 位寄存器,但我不知道如何使用内联汇编器来实现它。有人知道吗?
尽管如此,也可以对内在函数做同样的事情。 (如果您想要代码,请询问。)
此外,如果您知道数组的大小 t,则可以使用 Karatsuba 进一步优化计算。

【讨论】:

  • compiler output from this 在很多方面都很糟糕。首先,每个pclmul 都被movq 包围,以将64 位整数寄存器复制到xmm 寄存器的低半部分或从xmm 寄存器的低半部分复制。其次,xor 操作使用 32 位操作数大小,而不是合并到一个具有内存目标的 64 位异或。
  • 不,没有任何形式的 pclmul 可以在整数/gp 寄存器上运行,only xmm。将 128 位数据直接加载到 SSE 向量中并使用具有不同立即偏移量的 pclmul 来执行乘法的不同部分会更有效。如果您需要将 A 零扩展为 64 位元素,您可以在 SSE 中使用带有零寄存器的 punpckldq 执行此操作。 (或 SSE4.1 pmovzx)。实际上,你对 B 也做了同样的事情,但是是隐含的。
  • 您也应该使用 SSE 操作对 C 进行 XOR。 (从 C 加载,pxor,存储回来。)无论如何,你绝对应该使用内在函数,而不是内联 asm。您的内联汇编是正确且安全的,但您会损失很多性能。
  • 您可以使用a small change to the constraintsA[i] 直接加载到XMM 寄存器中。我认为您对 C 的 XOR 在 j 的迭代之间重叠,因此执行单个 64 位异或实际上可能更糟。您会从重叠中获得商店转发摊位。我希望编译器将高半部分保留在寄存器中以在下一次迭代中进行异或运算,因此每个迭代器都会有一个 reg-reg xor 和一个 reg-mem xor,但它没有这样做。无论如何,我链接的代码更好,但与应该可能的代码相比仍然很糟糕。