我如何将 MMX mulH 和 mulL 用于两个 64 位整数以获得一个 128 位整数

How do i use MMX mulH and mulL for two 64 bit integers to get one 128 bit integer

Hello, I'm working on yet another arbitrary precision integer library. I wanted to implement multiplication but I got stuck when _m_pmulhw in <mmintrin.h> just didn't work. there is very little documentation 在 MMX 指令上。当我测试它时,当我乘以两个 UINT64_MAXs 时,它只会给我乱码。

uint_fast64_t mulH(const uint_fast64_t &a, const uint_fast64_t &b)  {  
    return (uint_fast64_t)_m_pmulhw((__m64)a,(__m64)b);
}
uint_fast64_t mulL(const uint_fast64_t &a, const uint_fast64_t &b)  {  
    return (uint_fast64_t)_m_pmullw((__m64)a,(__m64)b);
}
int main() {
    uint64_t a = UINT64_MAX;
    uint64_t b = UINT64_MAX;
    std::cout <<  std::bitset<64>(mulH(a,b)) << std::bitset<64>(mulL(a,b));
}

output: 00000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000100000000000000010000000000000001 

我不知道为什么它不工作我有一个 A6-4400M APU...

coreinfo's 输出:MMX * Supports MMX instruction set

所以我想我可以说,它并非不受支持。如果有人可以给我一些如何使这项工作的提示,谢谢。

编译器:gcc

IDE: visual studio 代码

我不是这方面的专家,但根据 https://www.felixcloutier.com/x86/pmulhw,这些指令不会执行 64x64->128 乘法;他们做了四次 16x16->32 乘法。请注意描述中的单词 "packed"。此外,它是一个带符号的乘法。

因此您的 64 位 UINT64_MAX 值被解释为 0xffff 的四个字,也就是说 -1。因此,您将 -1 乘以 -1 四次。当然每一个的数字答案是1pmulhw指令的结果是结果的高半部分(即0x0000的四个字),pmullw是结果的低半部分(即0x0001的四个字) .

这正是你所得到的,所以在我看来,这些说明工作得很好。

如果你想对两个 64 位整数进行无符号乘法运算,普通的老式 mul 指令就可以满足你的目的,而让 gcc 生成它的最简单方法是可能通过将输入转换为 __uint128_t 并与通常的 * 运算符相乘。

我认为您误解了 _m_pmulhw 的作用。它实际上非常清楚地记录在英特尔的内部指南中:https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_m_pmulhw&expand=4340. The corresponding instruction is pmulhw, which is also clearly documented on e.g. Felix Cloutier's x86 instructions guide: https://www.felixcloutier.com/x86/pmulhw

它将四对16位整数乘以packed在两个操作数中,然后产生所有的高半部分四次乘法(压缩高位乘法 - 字)。这意味着,对于输入 0x12345678abcdef01、0x9876543210fedcba,它将乘以 0x1234 * 0x98760x5678 * 0x54320xabcd * 0x10fe0xef01 * 0xdcba,并将每个结果的高 16 位打包到输出中。

对于您的示例,您将 0xffff * 0xffff 乘以四次,产生 32 位结果 0x00000001-1 * -1,因为这是一个带符号的 16 位乘法),因此在高半部分得到 0x0000000000000000 ,在低半部分得到 0x0001000100010001 - 这正是你在 bitset 输出中看到的。


如果您正在寻找 128 位乘法,实际上并没有一个内在的(_mulx_u64 除外,但它使用了新的 mulx 指令,这不是那个广泛)。 Microsoft 具有非标准的 _mul128 内在函数,但在其他平台上,您可以只使用 __int128 类型(或本地等效类型)来获得 64x64=>128 位乘法。

此外,我强烈建议使用 SSE 指令集而不是旧的 MMX 指令集; SSE 指令在大多数情况下速度更快,使您能够在更广泛的向量类型上运行(现在 256 位是标准的,AVX512 现在可用),这可以显着提高速度。