当前位置: 首页 > 知识库问答 >
问题:

64位* 64位到128位的SIMD带符号乘法

楚俊迈
2023-03-14

我使用SIMD创建了一个64位*64位到128位的函数。目前我已经使用SSE2(实际上是SSE4.1)实现了它。这意味着它同时生产两个64b*64b到128b的产品。同样的想法可以扩展到AVX2或AVX512,同时提供四个或八个64b*64到128b的产品。我的算法基于http://www.hackersdelight.org/hdcodetxt/muldws.c.txt

算法执行一次无符号乘法、一次有符号乘法和两次有符号*无符号乘法。使用< code>_mm_mul_epi32和< code>_mm_mul_epu32,很容易完成有符号*有符号和无符号*无符号运算。但是混合的签名和未签名的产品给我带来了麻烦。比如说。

int32_t x = 0x80000000;
uint32_t y = 0x7fffffff;
int64_t z = (int64_t)x*y;

双字乘积应0xc000000080000000。但是,如果您假设编译器确实知道如何处理混合类型,那么如何获得它?这就是我想出的:

int64_t sign = x<0; sign*=-1;        //get the sign and make it all ones
uint32_t t = abs(x);                 //if x<0 take two's complement again
uint64_t prod = (uint64_t)t*y;       //unsigned product
int64_t z = (prod ^ sign) - sign;    //take two's complement based on the sign

使用SSE可以这样做

__m128i xh;    //(xl2, xh2, xl1, xh1) high is signed, low unsigned
__m128i yl;    //(yh2, yl2, yh2, yl2)
__m128i xs     = _mm_cmpgt_epi32(_mm_setzero_si128(), xh); // get sign
        xs     = _mm_shuffle_epi32(xs, 0xA0);              // extend sign
__m128i t      = _mm_sign_epi32(xh,xh);                    // abs(xh)
__m128i prod   = _mm_mul_epu32(t, yl);                     // unsigned (xh2*yl2,xh1*yl1)
__m128i inv    = _mm_xor_si128(prod,xs);                   // invert bits if negative
__m128i z      = _mm_sub_epi64(inv,xs);                    // add 1 if negative

这给出了正确的结果。但是我必须这样做两次(平方时一次),现在这是我功能的一个重要部分。有没有更有效的方法来使用SSE4.2、AVX2(四个128位产品)甚至AVX512(八个128位产品)来做到这一点?

也许有比SIMD更有效的方法来做到这一点?要得到上位词需要很多计算。

编辑:根据@ElderBug的评论,似乎不是用SIMD,而是用mul指令。值得一提的是,如果有人想知道这是多么复杂,这里有一个完整的工作功能(我刚开始工作,所以我没有优化它,但我认为它不值得)。

void muldws1_sse(__m128i x, __m128i y, __m128i *lo, __m128i *hi) {
    __m128i lomask = _mm_set1_epi64x(0xffffffff);

    __m128i xh     = _mm_shuffle_epi32(x, 0xB1);    // x0l, x0h, x1l, x1h
    __m128i yh     = _mm_shuffle_epi32(y, 0xB1);    // y0l, y0h, y1l, y1h

    __m128i xs     = _mm_cmpgt_epi32(_mm_setzero_si128(), xh);
    __m128i ys     = _mm_cmpgt_epi32(_mm_setzero_si128(), yh);
            xs     = _mm_shuffle_epi32(xs, 0xA0);
            ys     = _mm_shuffle_epi32(ys, 0xA0);

    __m128i w0     = _mm_mul_epu32(x,  y);          // x0l*y0l, y0l*y0h
    __m128i w3     = _mm_mul_epi32(xh, yh);         // x0h*y0h, x1h*y1h
            xh     = _mm_sign_epi32(xh,xh);
            yh     = _mm_sign_epi32(yh,yh);

    __m128i w1     = _mm_mul_epu32(x,  yh);         // x0l*y0h, x1l*y1h
    __m128i w2     = _mm_mul_epu32(xh, y);          // x0h*y0l, x1h*y0l

    __m128i yinv   = _mm_xor_si128(w1,ys);          // invert bits if negative
            w1     = _mm_sub_epi64(yinv,ys);         // add 1
    __m128i xinv   = _mm_xor_si128(w2,xs);          // invert bits if negative
            w2     = _mm_sub_epi64(xinv,xs);         // add 1

    __m128i w0l    = _mm_and_si128(w0, lomask);
    __m128i w0h    = _mm_srli_epi64(w0, 32);

    __m128i s1     = _mm_add_epi64(w1, w0h);         // xl*yh + w0h;
    __m128i s1l    = _mm_and_si128(s1, lomask);      // lo(wl*yh + w0h);
    __m128i s1h    = _mm_srai_epi64(s1, 32);

    __m128i s2     = _mm_add_epi64(w2, s1l);         //xh*yl + s1l
    __m128i s2l    = _mm_slli_epi64(s2, 32);
    __m128i s2h    = _mm_srai_epi64(s2, 32);           //arithmetic shift right

    __m128i hi1    = _mm_add_epi64(w3, s1h);
            hi1    = _mm_add_epi64(hi1, s2h);

    __m128i lo1    = _mm_add_epi64(w0l, s2l);
    *hi = hi1;
    *lo = lo1;
}

情况变得更糟。AVX512之前没有< code > _ mm _ srai _ epi 64 instrinsic/指令,因此我必须自己制作。

static inline __m128i _mm_srai_epi64(__m128i a, int b) {
    __m128i sra = _mm_srai_epi32(a,32);
    __m128i srl = _mm_srli_epi64(a,32);
    __m128i mask = _mm_set_epi32(-1,0,-1,0);
    __m128i out = _mm_blendv_epi8(srl, sra, mask);
}

我上面的< code>_mm_srai_epi64实现不完整。我想我用的是Agner Fog的Vector类库。如果您查看文件vectori128.h,您会发现

static inline Vec2q operator >> (Vec2q const & a, int32_t b) {
    // instruction does not exist. Split into 32-bit shifts
    if (b <= 32) {
        __m128i bb   = _mm_cvtsi32_si128(b);               // b
        __m128i sra  = _mm_sra_epi32(a,bb);                // a >> b signed dwords
        __m128i srl  = _mm_srl_epi64(a,bb);                // a >> b unsigned qwords
        __m128i mask = _mm_setr_epi32(0,-1,0,-1);          // mask for signed high part
        return  selectb(mask,sra,srl);
    }
    else {  // b > 32
        __m128i bm32 = _mm_cvtsi32_si128(b-32);            // b - 32
        __m128i sign = _mm_srai_epi32(a,31);               // sign of a
        __m128i sra2 = _mm_sra_epi32(a,bm32);              // a >> (b-32) signed dwords
        __m128i sra3 = _mm_srli_epi64(sra2,32);            // a >> (b-32) >> 32 (second shift unsigned qword)
        __m128i mask = _mm_setr_epi32(0,-1,0,-1);          // mask for high part containing only sign
        return  selectb(mask,sign,sra3);
    }
}

共有2个答案

南宫博简
2023-03-14

我找到了一个更简单的SIMD解决方案,它不需要< code >签名*未签名产品。< s >我不再相信SIMD(至少用AVX2和AV512)无法与< code>mulx竞争。在某些情况下,SIMD可以与< code>mulx竞争。我所知道的唯一情况是基于FFT的大数乘法。

诀窍是先做无符号乘法,然后再纠正。我从这个答案中学到了如何做到这一点,答案是32位有符号乘法,而不使用64位数据类型。对于(hi,lo)=x*y,首先执行无符号乘法,然后按如下方式更正hi

hi -= ((x<0) ? y : 0)  + ((y<0) ? x : 0)

这可以通过SSE4.2内在_mm_cmpgt_epi64完成

void muldws1_sse(__m128i x, __m128i y, __m128i *lo, __m128i *hi) {    
    muldwu1_sse(x,y,lo,hi);    
    //hi -= ((x<0) ? y : 0)  + ((y<0) ? x : 0);
    __m128i xs = _mm_cmpgt_epi64(_mm_setzero_si128(), x);
    __m128i ys = _mm_cmpgt_epi64(_mm_setzero_si128(), y);           
    __m128i t1 = _mm_and_si128(y,xs);
    __m128i t2 = _mm_and_si128(x,ys);
           *hi = _mm_sub_epi64(*hi,t1);
           *hi = _mm_sub_epi64(*hi,t2);
}

无符号乘法的代码更简单,因为它不需要混合有符号*无符号乘积。此外,由于它是无符号的,因此不需要算术右移,而算术右移只有AVX512的指令。事实上,以下函数只需要SSE2:

void muldwu1_sse(__m128i x, __m128i y, __m128i *lo, __m128i *hi) {    
    __m128i lomask = _mm_set1_epi64x(0xffffffff);

    __m128i xh     = _mm_shuffle_epi32(x, 0xB1);    // x0l, x0h, x1l, x1h
    __m128i yh     = _mm_shuffle_epi32(y, 0xB1);    // y0l, y0h, y1l, y1h

    __m128i w0     = _mm_mul_epu32(x,  y);          // x0l*y0l, x1l*y1l
    __m128i w1     = _mm_mul_epu32(x,  yh);         // x0l*y0h, x1l*y1h
    __m128i w2     = _mm_mul_epu32(xh, y);          // x0h*y0l, x1h*y0l
    __m128i w3     = _mm_mul_epu32(xh, yh);         // x0h*y0h, x1h*y1h

    __m128i w0l    = _mm_and_si128(w0, lomask);     //(*)
    __m128i w0h    = _mm_srli_epi64(w0, 32);

    __m128i s1     = _mm_add_epi64(w1, w0h);
    __m128i s1l    = _mm_and_si128(s1, lomask);
    __m128i s1h    = _mm_srli_epi64(s1, 32);

    __m128i s2     = _mm_add_epi64(w2, s1l);
    __m128i s2l    = _mm_slli_epi64(s2, 32);        //(*)
    __m128i s2h    = _mm_srli_epi64(s2, 32);

    __m128i hi1    = _mm_add_epi64(w3, s1h);
            hi1    = _mm_add_epi64(hi1, s2h);

    __m128i lo1    = _mm_add_epi64(w0l, s2l);       //(*)
    //__m128i lo1    = _mm_mullo_epi64(x,y);          //alternative

    *hi = hi1;
    *lo = lo1;
}

这使用

4x mul_epu32
5x add_epi64
2x shuffle_epi32
2x and
2x srli_epi64
1x slli_epi64
****************
16 instructions

AVX512具有_mm_mullo_epi64内在功能,可以用一条指令计算lo。在这种情况下,可以使用替代行(用 (*) 注释注释行并取消注释替代行):

5x mul_epu32
4x add_epi64
2x shuffle_epi32
1x and
2x srli_epi64
****************
14 instructions

要更改全宽AVX2的代码,请将_mm替换为_mm256,将si128替换为si256,并将__m128i替换为__m256i对于AVX512,将它们替换为_mm512si512__m512i

佟阳焱
2023-03-14

考虑使用各种指令的整数乘法的吞吐量限制的正确方法是根据每个周期可以计算多少“乘积位”。

mulx生成一个64x64-

如果您在SIMD上从执行32x32的指令中拼凑出一个乘数-

 类似资料:
  • 问题内容: 我已经使用Java一段时间了,而我典型的设置新开发机的习惯要求从Oracle站点下载并安装最新的JDK。 今天这引发了一个不寻常的问题, 回想起来,我已经安装了之前的两个版本,并且很高兴将普通的工具链插入(Eclipse)。在我的日常编程中,我不会回想起曾经因为使用64位JRE(或为此目的而针对64位JRE)而不得不以其他方式进行更改或思考的事情。 根据我对64位和32位的理解- 确实

  • 问题内容: 我想知道x86和x64中的64位长吗? 问题答案: 是。Java 在任何JVM上都是64位,无一例外。所有Java原语类型都是完全可移植的,并且在所有实现中都具有固定的大小。

  • 问题内容: 我在Redis上使用Lua,想比较两个带符号的64位数字,这些数字存储在两个8字节/字符的字符串中。 如何使用Redis中可用的库进行比较? http://redis.io/commands/EVAL#available- libraries 我想知道并检查。我认为这可能涉及为每个64位int提取两个32位数字,并对它们进行一些巧妙的数学运算,但是我不确定。 我有一些代码可以使这一过程

  • 所以我对这段代码有了一些了解: 在下面的所有内容中,我假设编译器不能对或的范围有任何先入为主的概念,初始化器仅用于上面的示例。 如果我在一个32位的整数编译器上编译这个(比如在编译x86的时候),没问题。编译器会简单地使用和作为类型值(不能进一步提升它们),乘法会简单地给出注释所说的结果(模在这种情况下是0x100000000)。 然而,如果我在一个64位整数大小的编译器上编译这个(例如x86-6

  • 问题内容: 我的电脑正在使用Windows 7 64位。但是将要部署我的jsp Web应用程序的服务器是32位。 我需要在PC上安装32位JDK / JRE才能进行开发吗?我正在使用Eclipse。 非常感谢你。 问题答案: 您绝对不需要安装32位JRE即可进行开发。您构建的Java代码不会跟踪您的64位。(我假设您没有使用JNI,这会使事情变得有些复杂。) 不过,您 可能 需要安装32位JRE进

  • 问题内容: 我正在创建一个非常简单的应用程序,该应用程序可以读取和显示文本文件并进行搜索。 我问自己是否有兴趣向用户提出32位和64位版本。 区别仅在于使用64位版本访问更多的内存堆大小,还是还有其他兴趣? 32位编译程序是否可以在64位JVM上运行(我认为是) 问题答案: 任何 程序的32位和64位版本之间的唯一区别是机器字的大小,可寻址内存的数量以及所使用的操作系统ABI。对于Java,语言规