実装ステージに入っています。雑多なコードをテンプレート化により整理します。実装では、何が良い実装か未だ確定しないので、パラメータは、テンプレート化して、後で変えるだけで、実装の中身も変えます。テンプレートが決まると固定した定数が出てくるので、さらにconst_exprにより静的にコンパイルするようにします。if文であっても、静的に評価されるので、実行時は、if文にはなりません。
一にアルゴリズム、二にデータ構造、三に実装テクニックですが、最後の部分でも数%の効果が期待できます。
WRITE_WIDTH=4,6,8,12,16,20,24で最適化コードを。
WRITE_WIDTH が 4,6,8,12,16,20,24 の固定値しか来ないなら、memcpy() に任せるより専用化した方が速いです。
ポイントは、
16byteまでは a0 の計算結果だけで完結
20,24byteは次の128bitも加算して一部書く
SSE2のみ使用
ストア数最小
template<int WRITE_WIDTH>
static inline void add_u8_aligned16_X(
unsigned char* A,
const unsigned char* B)
{
__m128i a0 = _mm_load_si128((const __m128i*)(A + 0));
__m128i b0 = _mm_load_si128((const __m128i*)(B + 0));
a0 = _mm_add_epi8(a0, b0);
if constexpr (WRITE_WIDTH == 4)
{
*(uint32_t*)A = (uint32_t)_mm_cvtsi128_si32(a0);
}
else if constexpr (WRITE_WIDTH == 6)
{
uint32_t lo32 = (uint32_t)_mm_cvtsi128_si32(a0);
uint16_t hi16 = (uint16_t)(_mm_extract_epi16(a0, 2));
*(uint32_t*)(A + 0) = lo32;
*(uint16_t*)(A + 4) = hi16;
}
else if constexpr (WRITE_WIDTH == 8)
{
_mm_storel_epi64((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 12)
{
_mm_storel_epi64((__m128i*)A, a0);
uint32_t x =
(uint32_t)_mm_extract_epi16(a0, 4)
| ((uint32_t)_mm_extract_epi16(a0, 5) << 16);
*(uint32_t*)(A + 8) = x;
}
else if constexpr (WRITE_WIDTH == 16)
{
_mm_store_si128((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 20)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
*(uint32_t*)(A + 16)
= (uint32_t)_mm_cvtsi128_si32(a1);
}
else if constexpr (WRITE_WIDTH == 24)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
_mm_storel_epi64((__m128i*)(A + 16), a1);
}
}
static inline void add_u8_aligned16_X(
unsigned char* A,
const unsigned char* B)
{
__m128i a0 = _mm_load_si128((const __m128i*)(A + 0));
__m128i b0 = _mm_load_si128((const __m128i*)(B + 0));
a0 = _mm_add_epi8(a0, b0);
if constexpr (WRITE_WIDTH == 4)
{
*(uint32_t*)A = (uint32_t)_mm_cvtsi128_si32(a0);
}
else if constexpr (WRITE_WIDTH == 6)
{
uint32_t lo32 = (uint32_t)_mm_cvtsi128_si32(a0);
uint16_t hi16 = (uint16_t)(_mm_extract_epi16(a0, 2));
*(uint32_t*)(A + 0) = lo32;
*(uint16_t*)(A + 4) = hi16;
}
else if constexpr (WRITE_WIDTH == 8)
{
_mm_storel_epi64((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 12)
{
_mm_storel_epi64((__m128i*)A, a0);
uint32_t x =
(uint32_t)_mm_extract_epi16(a0, 4)
| ((uint32_t)_mm_extract_epi16(a0, 5) << 16);
*(uint32_t*)(A + 8) = x;
}
else if constexpr (WRITE_WIDTH == 16)
{
_mm_store_si128((__m128i*)A, a0);
}WRITE_WIDTH=4,6,8,12,16,20,24で最適化コードを。
WRITE_WIDTH が 4,6,8,12,16,20,24 の固定値しか来ないなら、memcpy() に任せるより専用化した方が速いです。
ポイントは、
16byteまでは a0 の計算結果だけで完結
20,24byteは次の128bitも加算して一部書く
SSE2のみ使用
ストア数最小
template<int WRITE_WIDTH>
static inline void add_u8_aligned16_X(
unsigned char* A,
const unsigned char* B)
{
__m128i a0 = _mm_load_si128((const __m128i*)(A + 0));
__m128i b0 = _mm_load_si128((const __m128i*)(B + 0));
a0 = _mm_add_epi8(a0, b0);
if constexpr (WRITE_WIDTH == 4)
{
*(uint32_t*)A = (uint32_t)_mm_cvtsi128_si32(a0);
}
else if constexpr (WRITE_WIDTH == 6)
{
uint32_t lo32 = (uint32_t)_mm_cvtsi128_si32(a0);
uint16_t hi16 = (uint16_t)(_mm_extract_epi16(a0, 2));
*(uint32_t*)(A + 0) = lo32;
*(uint16_t*)(A + 4) = hi16;
}
else if constexpr (WRITE_WIDTH == 8)
{
_mm_storel_epi64((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 12)
{
_mm_storel_epi64((__m128i*)A, a0);
uint32_t x =
(uint32_t)_mm_extract_epi16(a0, 4)
| ((uint32_t)_mm_extract_epi16(a0, 5) << 16);
*(uint32_t*)(A + 8) = x;
}
else if constexpr (WRITE_WIDTH == 16)
{
_mm_store_si128((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 20)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
*(uint32_t*)(A + 16)
= (uint32_t)_mm_cvtsi128_si32(a1);
}
else if constexpr (WRITE_WIDTH == 24)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
_mm_storel_epi64((__m128i*)(A + 16), a1);
}
}
else if constexpr (WRITE_WIDTH == 20)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
*(uint32_t*)(A + 16)
= (uint32_t)_mm_cvtsi128_si32(a1);
}
else if constexpr (WRITE_WIDTH == 24)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
_mm_storel_epi64((__m128i*)(A + 16), a1);
}
}
WRITE_WIDTH が 4,6,8,12,16,20,24 の固定値しか来ないなら、memcpy() に任せるより専用化した方が速いです。
ポイントは、
- 16byteまでは
a0 の計算結果だけで完結 - 20,24byteは次の128bitも加算して一部書く
- SSE2のみ使用
- ストア数最小
template<int WRITE_WIDTH>
static inline void add_u8_aligned16_X(
unsigned char* A,
const unsigned char* B)
{
__m128i a0 = _mm_load_si128((const __m128i*)(A + 0));
__m128i b0 = _mm_load_si128((const __m128i*)(B + 0));
a0 = _mm_add_epi8(a0, b0);
if constexpr (WRITE_WIDTH == 4)
{
*(uint32_t*)A = (uint32_t)_mm_cvtsi128_si32(a0);
}
else if constexpr (WRITE_WIDTH == 6)
{
uint32_t lo32 = (uint32_t)_mm_cvtsi128_si32(a0);
uint16_t hi16 = (uint16_t)(_mm_extract_epi16(a0, 2));
*(uint32_t*)(A + 0) = lo32;
*(uint16_t*)(A + 4) = hi16;
}
else if constexpr (WRITE_WIDTH == 8)
{
_mm_storel_epi64((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 12)
{
_mm_storel_epi64((__m128i*)A, a0);
uint32_t x =
(uint32_t)_mm_extract_epi16(a0, 4)
| ((uint32_t)_mm_extract_epi16(a0, 5) << 16);
*(uint32_t*)(A + 8) = x;
}
else if constexpr (WRITE_WIDTH == 16)
{
_mm_store_si128((__m128i*)A, a0);
}
else if constexpr (WRITE_WIDTH == 20)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
*(uint32_t*)(A + 16)
= (uint32_t)_mm_cvtsi128_si32(a1);
}
else if constexpr (WRITE_WIDTH == 24)
{
__m128i a1 = _mm_load_si128((const __m128i*)(A + 16));
__m128i b1 = _mm_load_si128((const __m128i*)(B + 16));
a1 = _mm_add_epi8(a1, b1);
_mm_store_si128((__m128i*)(A + 0), a0);
_mm_storel_epi64((__m128i*)(A + 16), a1);
}
}