2011-11-19 100 views
11

我想乘以SSE4一个__m128i对象与16位无符号8位整数,但我只能找到一个内在乘法16位整数。有没有什么比如_mm_mult_epi8SSE乘法16 x uint8_t

+1

你能澄清一下你的问题吗?您是否要将每个16位8位整数的128位整数或16位8位整数的16位整数或16位8位整数乘以一个寄存器来相乘。前一种情况会有点奇怪。 –

+0

只是一个想法,但为什么不把8位到16位?如果你想测试溢出,你可以将AH与AH进行比较,看看是否匹配检查溢出。有点凌乱,只是在黑暗中刺伤。如果直接支持8位mul,也会让我感到惊讶,因为SIMD的指令集是为后8位处理器编写的 –

+0

@Paul:8位值仍在图形中使用。 AltiVec具有8位乘法,但每次只有8位乘法结果为16位。 – Potatoswatter

回答

11

MMX/SSE/AVX中没有8位乘法。但是,您可以模拟使用16位乘法8位乘法内在如下:

inline __m128i _mm_mullo_epi8(__m128i a, __m128i b) 
{ 
    __m128i zero = _mm_setzero_si128(); 
    __m128i Alo = _mm_cvtepu8_epi16(a); 
    __m128i Ahi = _mm_unpackhi_epi8(a, zero); 
    __m128i Blo = _mm_cvtepu8_epi16(b); 
    __m128i Bhi = _mm_unpackhi_epi8(b, zero); 
    __m128i Clo = _mm_mullo_epi16(Alo, Blo); 
    __m128i Chi = _mm_mullo_epi16(Ahi, Bhi); 
    __m128i maskLo = _mm_set_epi8(0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 14, 12, 10, 8, 6, 4, 2, 0); 
    __m128i maskHi = _mm_set_epi8(14, 12, 10, 8, 6, 4, 2, 0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80); 
    __m128i C = _mm_or_si128(_mm_shuffle_epi8(Clo, maskLo), _mm_shuffle_epi8(Chi, maskHi)); 

    return C; 
} 
8

唯一的8位SSE乘法指令是PMADDUBSW(SSSE3及更高版本,C/C++内部函数:_mm_maddubs_epi16)。这将16 x 8位无符号值乘以16 x 8位带符号的值,然后求和相邻对以给出8 x 16位带符号结果。如果你不能使用这个相当专业的指令,那么你需要解压缩成16位向量对,并使用常规的16位乘法指令。显然这意味着至少有2倍的吞吐量,所以如果可能的话使用8位乘法。

12

一个基于Agner Fog's solution不是萨芬的解决方案(可能)更快捷的方式:

而不是分裂高/低,分裂奇偶。这有一个额外的好处,它可以在纯SSE2中工作,而不需要SSE4.1(对于OP来说没有用处,但是对于一些用户来说是一个不错的附加奖励)。如果您有AVX2,我还添加了一项优化。从技术上讲,AVX2优化仅适用于SSE2内在函数,但比左右移位要慢。

__m128i mullo_epi8(__m128i a, __m128i b) 
{ 
    // unpack and multiply 
    __m128i dst_even = _mm_mullo_epi16(a, b); 
    __m128i dst_odd = _mm_mullo_epi16(_mm_srli_epi16(a, 8),_mm_srli_epi16(b, 8)); 
    // repack 
#ifdef __AVX2__ 
    // only faster if have access to VPBROADCASTW 
    return _mm_or_si128(_mm_slli_epi16(dst_odd, 8), _mm_and_si128(dst_even, _mm_set1_epi16(0xFF))); 
#else 
    return _mm_or_si128(_mm_slli_epi16(dst_odd, 8), _mm_srli_epi16(_mm_slli_epi16(dst_even,8), 8)); 
#endif 
} 

Agner使用blendv_epi8固有的SSE4.1支持。

编辑:

有趣的是,做更多的工作分解(具有优化的版本)后,至少我的两个实现被编译到完全一样的东西。以“ivy-bridge”(AVX)为目标的示例反汇编。

vpmullw xmm2,xmm0,xmm1 
vpsrlw xmm0,xmm0,0x8 
vpsrlw xmm1,xmm1,0x8 
vpmullw xmm0,xmm0,xmm1 
vpsllw xmm0,xmm0,0x8 
vpand xmm1,xmm2,XMMWORD PTR [rip+0x281] 
vpor xmm0,xmm0,xmm1 

它使用预编译的128位xmm常数的“AVX2优化”版本。仅编译SSE2支持会产生类似的结果(尽管使用SSE2指令)。我怀疑Agner Fog的原始解决方案可能会得到相同的优化(如果没有的话,会很疯狂)。不知道Marat的原始解决方案是如何在优化构建中进行比较的,尽管对于所有x86 simd扩展包含比SSE2更新并且包含SSE2的方法,这是相当不错的。

+2

这真的很好。它利用了有符号与无符号只影响N×N - > 2N位乘法的高一半的事实,并且[高位中的垃圾不会影响你想要的低位结果]( http://stackoverflow.com/questions/34377711/which-2s-complement-integer-operations-can-be-used-without-zeroing-high-bits-in)。如果在加载掩码时缓存未命中是一个问题,您可以使用2个insns产生它:'pcmpeqw xmm7,xmm7' /'psrlw xmm7,8'。 (有关其他常量生成序列,请参阅http://stackoverflow.com/q/35085059/224132)。 –

+1

这很整洁,我看到[clang优化左移/右移到带有常量掩码的vpand](http://goo.gl/GmFc9H)!这可能是更好的代码,除非掩码容易在缓存中丢失。 gcc不会做这种优化。 shift和mask之间的选择完全不依赖于AVX2。它取决于内存中的一个大常量是否是你想要的。 (我注意到没有avx,clang最后浪费了一个movdqa:它可能在第二个pmul使用了'pmullw xmm0,xmm1',并在'xmm0'(返回值寄存器)中建立了最终结果。 –

+1

您的评论关于'vpbroadcastw'是完全错误的:大多数编译器不会将'set1'编译成常量的运行时广播,因为它很昂贵。'mov eax,0xff' /'movd xmm0,eax'/vpbroadcastw xmm0,xmm0' is Haswell上的3个uops,'vpbroadcastw xmm0,[mem16]'也是3个uops,动态生成比两者都便宜(但编译器倾向于把它们放在内存中),然而'vpbroadcastd'只有1个uop,即使没有熔断:它只需要一个加载端口,而不是ALU,所以你不需要浪费32B的内存在一个将被加载到循环外部的常量上 –