X86 2个64位整数的SSE乘法

X86 2个64位整数的SSE乘法,x86,sse,simd,multiplication,sse2,X86,Sse,Simd,Multiplication,Sse2,如何将两个64位整数乘以另外两个64位整数? 我找不到任何可以执行此操作的指令。您需要使用32位乘法运算实现自己的64位乘法例程。不过,这可能不会比使用标量代码更有效,尤其是当需要对向量进行大量洗牌以获得所有必需的操作时。我知道这是一个老问题,但实际上我一直在寻找这个问题。因为仍然没有相关的指令,我自己用Paul R提到的pmuldq实现了64位乘法。这就是我想到的: // requires g++ -msse4.1 ... #include <emmintrin.h> #incl

如何将两个64位整数乘以另外两个64位整数?
我找不到任何可以执行此操作的指令。

您需要使用32位乘法运算实现自己的64位乘法例程。不过,这可能不会比使用标量代码更有效,尤其是当需要对向量进行大量洗牌以获得所有必需的操作时。

我知道这是一个老问题,但实际上我一直在寻找这个问题。因为仍然没有相关的指令,我自己用Paul R提到的pmuldq实现了64位乘法。这就是我想到的:

// requires g++ -msse4.1 ...

#include <emmintrin.h>
#include <smmintrin.h>

__m128i Multiply64Bit(__m128i a, __m128i b)
{
    auto ax0_ax1_ay0_ay1 = a;
    auto bx0_bx1_by0_by1 = b;

    // i means ignored

    auto ax1_i_ay1_i = _mm_shuffle_epi32(ax0_ax1_ay0_ay1, _MM_SHUFFLE(3, 3, 1, 1));
    auto bx1_i_by1_i = _mm_shuffle_epi32(bx0_bx1_by0_by1, _MM_SHUFFLE(3, 3, 1, 1));

    auto ax0bx0_ay0by0 = _mm_mul_epi32(ax0_ax1_ay0_ay1, bx0_bx1_by0_by1);
    auto ax0bx1_ay0by1 = _mm_mul_epi32(ax0_ax1_ay0_ay1, bx1_i_by1_i);
    auto ax1bx0_ay1by0 = _mm_mul_epi32(ax1_i_ay1_i, bx0_bx1_by0_by1);

    auto ax0bx1_ay0by1_32 = _mm_slli_epi64(ax0bx1_ay0by1, 32);
    auto ax1bx0_ay1by0_32 = _mm_slli_epi64(ax1bx0_ay1by0, 32);

    return _mm_add_epi64(ax0bx0_ay0by0, _mm_add_epi64(ax0bx1_ay0by1_32, ax1bx0_ay1by0_32));
}
//需要g++-msse4.1。。。
#包括
#包括
__m128i乘法64位(_m128i a,__M128iB)
{
自动ax0\u ax1\u ay0\u ay1=a;
自动bx0_bx1_by0_by1=b;
//我的意思是被忽视
自动ax1_i_ay1_i=_mm_shuffle_epi32(ax0_ax1_ay0_ay1,_mm_shuffle(3,3,1,1));
自动bx1_i_by1_i=_mm_shuffle_epi32(bx0_bx1_by0_by1,_mm_shuffle(3,3,1,1));
自动ax0bx0\u ay0by0=\u mm\u mul\u epi32(ax0\u ax1\u ay0\u ay1,bx0\u bx1\u by0\u by1);
自动ax0bx1_ay0by1=_mm_mul_epi32(ax0_ax1_ay0_ay1,bx1_i_by1_i);
自动ax1bx0_ay1by0=_mm_mul_epi32(ax1_i_ay1_i,bx0_bx1_by0_by1);
自动ax0bx1_ay0by1_32=_mm_slli_epi64(ax0bx1_ay0by1,32);
自动ax1bx0_ay1by0_32=_mm_slli_epi64(ax1bx0_ay1by0,32);
返回_mm_add_epi64(ax0bx0_ay0by0,_mm_add_epi64(ax0bx1_ay0by1_32,ax1bx0_ay1by0_32));
}

Godbolt at.

迟来的回答,但这是Barabas发布的更好的版本

如果您曾经使用过GCC或Clang的向量扩展,这就是他们使用的例程

这与长乘法和网格乘法使用的方法相同

    65
  * 73
  ----
    15 //   (5 * 3)
   180 //   (6 * 3) * 10
   350 //   (5 * 7) * 10
+ 4200 // + (6 * 7) * 100
------
  4745
但是,它不是以10为单位,而是以32位为单位,并省略了最后一次乘法,因为它总是会移过第64位,就像截断大于99的值时不乘以6*7一样

#include <emmintrin.h>

/*
 * Grid/long multiply two 64-bit SSE lanes.
 * Works for both signed and unsigned.
 *   ----------------.--------------.----------------.
 *  |                |   b >> 32    | a & 0xFFFFFFFF |
 *  |----------------|--------------|----------------|  
 *  | d >> 32        |   b*d << 64  |    a*d << 32   |
 *  |----------------|--------------|----------------|
 *  | c & 0xFFFFFFFF |   b*c << 32  |       a*c      |
 *  '----------------'--------------'----------------'
 *  Add all of them together to get the product.
 *
 *  Because we truncate the value to 64 bits, b*d << 64 will be zero,
 *  so we can leave it out.
 *
 *  We also can add a*d and b*c first and then shift because of the
 *  distributive property: (a << 32) + (b << 32) == (a + b) << 32.
 */

__m128i Multiply64Bit(__m128i ab, __m128i cd)
{
    /* ac = (ab & 0xFFFFFFFF) * (cd & 0xFFFFFFFF); */
    __m128i ac = _mm_mul_epu32(ab, cd);

    /* b = ab >> 32; */
    __m128i b = _mm_srli_epi64(ab, 32);

    /* bc = b * (cd & 0xFFFFFFFF); */
    __m128i bc = _mm_mul_epu32(b, cd);

    /* d = cd >> 32; */
    __m128i d = _mm_srli_epi64(cd, 32);

    /* ad = (ab & 0xFFFFFFFF) * d; */
    __m128i ad = _mm_mul_epu32(ab, d);

    /* high = bc + ad; */
    __m128i high = _mm_add_epi64(bc, ad);

    /* high <<= 32; */
    high = _mm_slli_epi64(high, 32);

    /* return ac + high; */
    return _mm_add_epi64(high, ac);
}
#包括
/*
*栅格/长乘法两个64位SSE通道。
*适用于已签名和未签名。
*   ----------------.--------------.----------------.
*| | b>>32 | a&0xFFFFFFFF|
*  |----------------|--------------|----------------|  

*| d>>32 | b*d在本文中,“两个64位整数”是什么意思?您是指一对64位整数(la复数)还是一对64位整数表示的单个128位整数?我是指一对64位整数表示的单个m128i位整数可能与的重复。相关:对于AVX2或SSE4.1,性能分析与64位标量代码(如果你还没有SIMD向量中的数据。)从我的头顶上看,SSE4中不是有一个
pmuldqq
或者添加了什么吗?SSE4中有一个
pmuldq
,它是32x32=>64位的乘法,所以你可以用它作为64x64位乘法的构建块。你知道最好的标量算法吗(假设您只有32位硬件)?这就是我要做的。我会将每个数字分成其上下32位部分,然后执行(ab)=(al+ah)*(blbh)=t1+t2+t3+t4,其中t1=albl,t2=albh,t3=ahbl t4=ahbh。每个项将是一个64位数字。然后t2和t3必须再次拆分为低和高,最终数字将是(ab)l=t1+t2l+t3l,(ab)h=t4+t2h+t3h+c,其中c是(a*b)的任何进位l、 这是4个mult,7个adds。这是真的吗?我自己从来没有实现过,但它应该是你建议的方法。我无法想象它会非常有效,所以可能只有当你有其他64位SIMD操作要与之结合时才值得。在Sandy Bridge上,通用multipli阳离子和向量乘法被发布到不同的端口,因此如果您执行多组乘法,您可能可以免费获得SSE乘法。但是,加法和洗牌将是一个问题。如果您执行不太需要端口5的操作,这些操作也可能免费提供。您对cod进行过基准测试吗与使用通用寄存器相比?我对结果很感兴趣,因为我正在进行大量的64×64位乘法。我刚刚做了一些基准测试,它仍然比标量乘法(用cl/O2编译)快大约831600000次平均乘法。在我功能强大的i7 5820k上0.37秒。同时,相同的标量乘法在平均乘法上花费了1.71次。所以它大约快4倍,这有点奇怪。我想cl非常擅长优化超标量指令
\u mm_mul_epi32
是SSE4.1指令。
\u mm_mul_epu32
是SSE2指令。
\u mm\u mul\u epu32
产生更好的代码,但它需要无符号类型。使用
-march=skylake-avx512
,我们得到AVX512DQ
vpmulqq
:)AVX512最终引入了64位元素大小整数乘法。顺便说一句,如果没有AVX2,使用SIMD进行64x64=>64位乘法可能不值得,除非您已经有了向量中的数据。(每64位整数一个标量
imul r64,r/m64
uop非常好。)。我的答案是使用
mullo_epi32
(SSE4.1或AVX2)以同时获得两个低*高产品,尽管
pmulld
在英特尔CPU上需要2个UOP。是的。我想指出的是,Neon使用的方法也可以做到这一点,它使用vrev64(32位字交换)和4xi32乘法、vpaddl(成对加法),左移,然后长乘法累加。如果SSE有一个成对的加法,那可能会更快,但考虑到NEON_2_SSE对该指令进行了标量化,我认为它不会。但它会解码为两个随机数,提供一个垂直的
padd
uop;不使用它会更快。我还没有查看链接答案的细节,但它是does提到使用psrlq/paddq/pand(3个总计量单位)代替phadd+pshufd(3个随机计量单位+一个添加)。更多说明b