サラリと検索して見当たらなかったので、覚書として、
SSEでエンディアンを変換したいときにどうすればいいか。
SSE2までのSSE命令を使っています。
コンパイラはVisualC++を使っています。
構文チェックをしていないので、セミコロンなどが抜けてるかもしれません。
16bitづつをひっくり返す場合。
- _mm_sll_epi16()及び_mm_srl_epi16()で16bitづつに区切って8bitづつシフト。
- それらをORで結合して出来上がり。
swap_word.cpp
/**
* change byte endian in 8 words
*
* @note src = 1,2,3,4, ..., 16
* srca = 0,1,0,3, ..., 15
* srcb = 2,0,4,0, ..., 0
* result = 2,1,4,3, ..., 15
*/
__m128i swap_epi16(__m128i src)
{
__declspec(align(16)) count8[2] = {
8, 8
};
__m128i srca = _mm_sll_epi16(src, *(__m128i*)count8);
__m128i srcb = _mm_srl_epi16(src, *(__m128i*)count8);
__m128i result = _mm_or_si128(srca, srcb);
return result;
}
32bitづつをひっくり返す場合。
- _mm_sll_epi16()及び_mm_srl_epi16()で16bitづつに区切って8bitづつシフト。
- それらをORで結合して第1段階完了。
- 更にそれを32bitづつに区切って16bitづつシフト。
- それらをORで結合して出来上がり。
swap_dword.cpp
/**
* change byte endian in 4 dwords
*
* @note src = 1,2,3,4, ..., 16
* srca = 0,1,0,3, ..., 15
* srcb = 2,0,4,0, ..., 0
* srcc = 2,1,4,3, ..., 15
* srca = 0,0,2,1, ..., 13
* srcb = 4,3,0,0, ..., 0
* result = 4,3,2,1, ..., 13
*/
__m128i swap_epi32(__m128i src)
{
__declspec(align(16)) count8[2] = {
8, 8
};
__declspec(align(16)) count16[2] = {
16, 16
};
__m128i srca = _mm_sll_epi16(src, *(__m128i*)count8);
__m128i srcb = _mm_srl_epi16(src, *(__m128i*)count8);
__m128i srcc = _mm_or_si128(srca, srcb);
srca = _mm_sll_epi32(srcc, *(__m128i*)count16);
srcb = _mm_srl_epi32(srcc, *(__m128i*)count16);
//__m128i result = _mm_or_si128(src1a, src1b); 誤り
__m128i result = _mm_or_si128(srca, srcb);
return result;
}
count8をレジスタに読み込んどいたほうが速そうとか、count16はcount8を2倍(1bit左シフト)すれば使えるとかはお任せします。
64bitづつをひっくり返す場合。
- _mm_sll_epi16()及び_mm_srl_epi16()で16bitづつに区切って8bitづつシフト。
- それらをORで結合して第1段階完了。
- 更にそれを32bitづつに区切って16bitづつシフト。
- それらをORで結合して第2段階完了。
- 更にそれを64bitづつに区切って32bitづつシフト。
- それらをORで結合して出来上がり。
swap_qword.cpp
/**
* change byte endian in 2 qwords
*
* @note src = 1,2,3,4, ..., 16
* srca = 0,1,0,3, ..., 15
* srcb = 2,0,4,0, ..., 0
* srcc = 2,1,4,3, ..., 15
* srca = 0,0,2,1, ..., 13
* srcb = 4,3,0,0, ..., 0
* srcc = 4,3,2,1, ..., 13
* srca = 0,0,0,0, ..., 9
* srcb = 8,7,6,5, ..., 0
* result = 8,7,6,5, ..., 9
*/
__m128i swap_epi64(__m128i src)
{
__declspec(align(16)) count8[2] = {
8, 8
};
__declspec(align(16)) count16[2] = {
16, 16
};
__declspec(align(16)) count32[2] = {
32, 32
};
__m128i srca = _mm_sll_epi16(src, *(__m128i*)count8);
__m128i srcb = _mm_srl_epi16(src, *(__m128i*)count8);
__m128i srcc = _mm_or_si128(srca, srcb);
srca = _mm_sll_epi32(srcc, *(__m128i*)count16);
srcb = _mm_srl_epi32(srcc, *(__m128i*)count16);
srcc = _mm_or_si128(srca, srcb);
srca = _mm_sll_epi64(srcc, *(__m128i*)count32);
srcb = _mm_srl_epi64(srcc, *(__m128i*)count32);
//__m128i result = _mm_or_si128(src1a, src1b); 誤り
__m128i result = _mm_or_si128(srca, srcb);
return result;
}
count8をレジスタに読み込んどいたほうが速そうとか、count32/count16はcount8を4/2倍(2/1bit左シフト)すれば使えるとかはお任せします。
avxで4バイトづつ(2023/06/21追記)
最近のCPUはavxまで載っていたりするので、shuffle命令版。
swap_4byte_avx.c
__m256i swap_epi32_avx(__m256i src)
__m256i ptn = _mm256_set_epi32(
0x0C0D0E0F, 0x08090A0B, 0x04050607, 0x00010203,
0x0C0D0E0F, 0x08090A0B, 0x04050607, 0x00010203);
return _mm256_shuffle_epi8(src, ptn);
}
__m128i swap_epi32_sse(__m128i src)
__m128i ptn = _mm_set_epi32(0x0C0D0E0F, 0x08090A0B, 0x04050607, 0x00010203);
// __m128i ptn = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
return _mm_shuffle_epi8(src, ptn);
}
参考
こちらをかなり参考にさせて頂いています。--> http://www.officedaytime.com/tips/simd.html
余談
- Visual C++であれば、_byteswap_uint64(), _byteswap_ulong(), _byteswap_ushort()といった便利な関数があるので、SIMDにしなくていい時はこれを使うのが良いのではないかと。
- この調子だと、4bitづつのスワップも出来るのかと思ったら、8bit単位のビットシフト命令は無かったw