SSE 系列內置函數中的 shuffle 函數
郵箱: quarrying@qq.com
博客: http://www.cnblogs.com/quarryman/
發布時間: 2017年04月18日
版權聲明: 自由分享, 保持署名-非商業用途-非衍生, 知識共享3.0協議。
水平有限, 歡迎大家批評指正!
這份博文總結了 SSE 系列內置函數中與 shuffle 有關的宏和函數。分析驗證了 _mm_shuffle_epi16
存在的可能性,並利用 _mm_shuffle_epi8
實現了該函數。
下面是 SSE 系列內置函數中與 shuffle 有關的 intrinsic 函數:
// Integer shuffle, SSE
extern __m64 _mm_shuffle_pi16(__m64 _A, int _Imm);
// SP FP shuffle, SSE
extern __m128 _mm_shuffle_ps(__m128 _A, __m128 _B, unsigned int _Imm);
// DP FP shuffle, SSE2
extern __m128d _mm_shuffle_pd(__m128d _A, __m128d _B, int _Imm);
// Integer shuffle, SSE2
extern __m128i _mm_shuffle_epi32(__m128i _A, int _Imm);
extern __m128i _mm_shufflehi_epi16(__m128i _A, int _Imm);
extern __m128i _mm_shufflelo_epi16(__m128i _A, int _Imm);
// Integer shuffle, SSSE3
extern __m128i _mm_shuffle_epi8 (__m128i a, __m128i b);
extern __m64 _mm_shuffle_pi8 (__m64 a, __m64 b);
1. _mm_shuffle_pd
和 _MM_SHUFFLE2
可以使用 _MM_SHUFFLE2
來構造 _mm_shuffle_pd
中的參數 _Imm
, _MM_SHUFFLE2
是 SSE2
中定義的一個宏, 定義如下
#define _MM_SHUFFLE2(x,y) (((x)<<1) | (y))
因為 __m128d
能容納 2 個 DP FP
(雙精度浮點數) , 所以 x,y
的取值集合為 {0, 1}
, _MM_SHUFFLE2(x,y)
的最大值為
(((1)<<1) | (1)) == 3 <= 2^32 - 1
顯然可以用 int
來表示立即數 _Imm
.
2. _mm_shuffle_ps
和 _MM_SHUFFLE
可以使用 _MM_SHUFFLE
來構造 _mm_shuffle_ps
中的參數 _Imm
, _MM_SHUFFLE
是 SSE
中定義的一個宏, 定義如下
#define _MM_SHUFFLE(fp3, fp2, fp1, fp0) (((fp3) << 6) | ((fp2) << 4) | \
((fp1) << 2) | ((fp0)))
因為 __m128
能容納 4 個 SP FP
(單精度浮點數) , 所以 fp3, fp2, fp1, fp0
的取值集合為 {0, 1, 2, 3}
, _MM_SHUFFLE(fp3, fp2, fp1, fp0)
的最大值為
(((3) << 6) | ((3) << 4) | ((3) << 2) | ((3))) == 255 == 2^8 - 1 <= 2^32 - 1
顯然可以用 unsigned int
來表示立即數 _Imm
.
注意: 文檔和代碼注釋中只說到使用 _MM_SHUFFLE
來構造 _mm_shuffle_ps
中的參數 _Imm
, 實際上除了 _mm_shuffle_ps
, 上面的 _mm_shuffle_pi16
, _mm_shuffle_epi32
, _mm_shufflehi_epi16
, _mm_shufflelo_epi16
, 都可以使用 _MM_SHUFFLE
來構造 _Imm
.
3. _mm_shuffle_epi16
和 _MM_SHUFFLE8
SSE
系列內置函數中並沒有定義 _mm_shuffle_epi16
, 而是定義了 _mm_shufflehi_epi16
, _mm_shufflelo_epi16
.
我們不妨定義 __m128i _mm_shuffle_epi16(__m128i _A, int _Imm);
, 且定義 _MM_SHUFFLE8
來構造第二個參數 _Imm
, 定義如下
#define _MM_SHUFFLE8(fp7, fp6, fp5, fp4, fp3, fp2, fp1, fp0)\
(((fp7) << 21) | ((fp6) << 18) | ((fp5) << 15) | ((fp4) << 12)) |\
(((fp3) << 9) | ((fp2) << 6) | ((fp1) << 3) | ((fp0)))
因為 __m128i
能容納 8 個 16 位整型數據, 所以fp7, fp6, fp5, fp4, fp3, fp2, fp1, fp0
的取值集合為 {0, 1, 2, 3, 4, 5, 6, 7}
, 則 _MM_SHUFFLE8(fp7, fp6, fp5, fp4, fp3, fp2, fp1, fp0)
的最大值為
(((7) << 21) | ((7) << 18) | ((7) << 15) | ((7) << 12)) |
(((7) << 9) | ((7) << 6) | ((7) << 3) | ((7))) == 16777215 == 2^24 - 1 <= 2^32 - 1
所以上面定義的 _mm_shuffle_epi16
理論上是可行的, 然而 SSE 系列內置函數中卻沒有這個函數, 我的猜測是:
_mm_shufflehi_epi16
, _mm_shufflelo_epi16
可以使用 _MM_SHUFFLE
構造立即數 _Imm
, 而要實現 _mm_shuffle_epi16
, 還要配套實現 _MM_SHUFFLE8
, 開發人員容易弄混各種 shuffle
相關的宏和函數.實際上, 可以利用 _mm_shufflehi_epi16
和 _mm_shufflelo_epi16
來實現 _mm_shuffle_epi16
( 可能比較麻煩), 也可以利用 SSSE3 中的 _mm_shuffle_epi8
來實現 _mm_shuffle_epi16
.
3.1 代碼一, 利用 _mm_shuffle_epi8
實現 _mm_shuffle_epi16
/*
博客: http://www.cnblogs.com/quarryman/
發布時間: 2017年04月18日
版權聲明: 自由分享, 保持署名-非商業用途-非衍生, 知識共享3.0協議。
如有錯誤和建議, 歡迎發郵件或留言!
*/
#include <stdio.h>
#include <emmintrin.h>
#include <tmmintrin.h>
#define _MM_SHUFFLE8(fp7, fp6, fp5, fp4, fp3, fp2, fp1, fp0)\
(((fp7) << 21) | ((fp6) << 18) | ((fp5) << 15) | ((fp4) << 12)) | \
(((fp3) << 9) | ((fp2) << 6) | ((fp1) << 3) | ((fp0)))
__m128i _mm_shuffle_epi16(__m128i _A, int _Imm)
{
_Imm &= 0xffffff;
char m01 = (_Imm >> 0) & 0x7, m03 = (_Imm >> 3) & 0x7;
char m05 = (_Imm >> 6) & 0x7, m07 = (_Imm >> 9) & 0x7;
char m09 = (_Imm >> 12) & 0x7, m11 = (_Imm >> 15) & 0x7;
char m13 = (_Imm >> 18) & 0x7, m15 = (_Imm >> 21) & 0x7;
m01 <<= 1; m03 <<= 1; m05 <<= 1; m07 <<= 1;
m09 <<= 1; m11 <<= 1; m13 <<= 1; m15 <<= 1;
char m00 = m01 + 1, m02 = m03 + 1, m04 = m05 + 1, m06 = m07 + 1;
char m08 = m09 + 1, m10 = m11 + 1, m12 = m13 + 1, m14 = m15 + 1;
//__m128i vMask = _mm_set_epi8(m00, m01, m02, m03, m04, m05, m06, m07,
// m08, m09, m10, m11, m12, m13, m14, m15);
__m128i vMask = _mm_set_epi8(m14, m15, m12, m13, m10, m11, m08, m09,
m06, m07, m04, m05, m02, m03, m00, m01);
return _mm_shuffle_epi8(_A, vMask);
}
void icvPrintM128i_16s(const __m128i& v)
{
printf("(%d, %d, %d, %d, %d, %d, %d, %d)",
v.m128i_i16[0], v.m128i_i16[1], v.m128i_i16[2], v.m128i_i16[3],
v.m128i_i16[4], v.m128i_i16[5], v.m128i_i16[6], v.m128i_i16[7]);
}
int main()
{
__m128i val = _mm_setr_epi16(0, 1, 2, 3, 4, 5, 6, 7);
icvPrintM128i_16s(val);
val = _mm_shuffle_epi16(val, _MM_SHUFFLE8(1, 2, 3, 5, 4, 7, 6, 0));
icvPrintM128i_16s(val);
getchar();
return 0;
}
4. _mm_shuffle_epi8
注意到 _mm_shuffle_epi8
的第二個參數使用的是 __m128i
, 而不是 int
或 unsigned int
. 可以同理得到第二個參數理論上的最大值:
(((15) << 60) | ((15) << 56) | ((15) << 52) | ((15) << 48)) |
(((15) << 44) | ((15) << 40) | ((15) << 36) | ((15) << 32)) |
(((15) << 28) | ((15) << 24) | ((15) << 20) | ((15) << 16)) |
(((15) << 12) | ((15) << 8) | ((15) << 4) | ((15))) == 2**64 - 1
所以不能用 32 位整型表示, 卻能用 128 位的 __m128i
表示.